[英]Needing clarity on auto return type deduction for friend operator+ in a class template
I'm just wanting some clarity here with what is happening with this code and why it is behaving as such: 我只是想清楚一下这段代码发生了什么,以及为什么它的表现如下:
main.cpp main.cpp中
#include <fstream>
#include <iostream>
#include <iomanip>
#include <sstream>
#include "Register.h"
int main() {
using namespace vpc;
Reg8 r8{ 0xEF };
Reg16 expected{ 478 };
Reg16 r16a = r8 + r8;
Reg16 r16b{ r8 + r8 };
std::cout << expected << r16a << r16b;
return EXIT_SUCCESS;
}
The code in my main function doesn't change as it is the same for both cases: 我的main函数中的代码没有改变,因为两种情况都是一样的:
This is my operator+
that I'm working on and this was my first attempt: 这是我的
operator+
我正在努力,这是我的第一次尝试:
template<typename Lhs, typename Rhs>
auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {
auto tmp = l.value + r.value;
if (sizeof(l.value) < sizeof(r.value))
return Register<Rhs>{ tmp };
else
return Register<Lhs>{ tmp };
}
And this was the program's output: 这是该计划的输出:
Output v1 输出v1
Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110
Reg16(222)
Prev: 0
hex: 0x00DE
bin: 0000000011011110
Reg16(222)
Prev: 0
hex: 0x00DE
bin: 0000000011011110
As you can see above the expected value should be 478
in decimal or 0x01DE
in hex. 如您所见,预期值应为十进制
478
或十六进制0x01DE
。 However the operator=
and the Register<T>
constructors are not getting the appropriate value from the operator+
in this case. 但是,在这种情况下,
operator=
和Register<T>
构造函数没有从operator+
获得适当的值。
I was able to resolve this issue by changing my operator+
to this: 我能够通过将
operator+
更改为此来解决此问题:
template<typename Lhs, typename Rhs>
auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {
return Register<decltype(l.value + r.value)>{ l.value + r.value };
}
And this is giving me the correct result: 这给了我正确的结果:
Output v2 输出v2
Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110
Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110
Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110
If you need to see my full class implementation you can find it below my question(s) - concern(s): I'm looking for clarity and a better understanding of the behaviors here. 如果您需要查看我的完整课程实现,您可以在我的问题下面找到它 - 关注点:我正在寻找清晰度并更好地理解这里的行为。
What I would like to know is why is the first version not producing the correct or expected value and why the second attempt does. 我想知道的是为什么第一个版本没有产生正确或预期的值以及第二个尝试的原因。 What are the main differences between the two implementations as well as what is going on under the hood with in the compiler?
这两个实现之间的主要区别是什么,以及编译器内部的内容是什么? I'm using Visual Studio 2017.
我正在使用Visual Studio 2017。
Register.h Register.h
#pragma once
#include <algorithm>
#include <bitset>
#include <cassert>
#include <climits>
#include <cstdint>
#include <iterator>
#include <iostream>
#include <iomanip>
#include <limits>
#include <map>
#include <string>
#include <type_traits>
namespace vpc {
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
template<typename T>
struct Register;
using Reg8 = Register<u8>;
using Reg16 = Register<u16>;
using Reg32 = Register<u32>;
using Reg64 = Register<u64>;
template<typename T>
struct Register {
T value;
T previous_value;
std::bitset<sizeof(T)* CHAR_BIT> bits;
Register() : value{ 0 }, previous_value{ 0 }, bits{ 0 } {}
template<typename U, std::enable_if_t<(sizeof(U) > sizeof(T))>* = nullptr>
explicit Register(const U val, const u8 idx = 0) :
value{ static_cast<T>((val >> std::size(bits) * idx) &
std::numeric_limits<std::make_unsigned_t<T>>::max()) },
previous_value{ 0 },
bits{ value }
{
constexpr u16 sizeT = sizeof(T);
constexpr u16 sizeU = sizeof(U);
assert((idx >= 0) && (idx <= ((sizeU / sizeT) - 1)) );
}
template<typename U, std::enable_if_t<(sizeof(U) < sizeof(T))>* = nullptr>
explicit Register(const U val, const u8 idx = 0) :
value{ static_cast<T>((static_cast<T>(val) << sizeof(U)*CHAR_BIT*idx) &
std::numeric_limits<std::make_unsigned_t<T>>::max()) },
previous_value{ 0 },
bits{ value }
{
constexpr u16 sizeT = sizeof(T);
constexpr u16 sizeU = sizeof(U);
assert((idx >= 0) && (idx <= ((sizeT / sizeU) - 1)) );
}
template<typename U, std::enable_if_t<(sizeof(U) == sizeof(T))>* = nullptr>
explicit Register(const U val, const u8 idx = 0) :
value{ static_cast<T>( val ) }, previous_value{ 0 }, bits{ value }
{}
template<typename... Args>
Register(Args... args) {}
template<typename U>
Register(const Register<U>& reg, const u8 idx = 0) : Register(reg.value, idx) {}
void changeEndian() {
T tmp = value;
char* const p = reinterpret_cast<char*>(&tmp);
for (size_t i = 0; i < sizeof(T) / 2; ++i)
std::swap(p[i], p[sizeof(T) - i - 1]);
bits = tmp;
}
Register& operator=(const Register& obj) {
this->value = obj.value;
this->previous_value = obj.previous_value;
this->bits = obj.bits;
return *this;
}
template<typename Lhs, typename Rhs>
friend auto operator+(const Register<Lhs>& l, const Register<Rhs>& r);
};
} // namespace vpc
Three points. 三点。
(1) Remember to use if constexpr
, instead of simply if
, in your first version of operator+ ()
(1)记得要用
if constexpr
,而不是简单的if
,在你的第一个版本的operator+ ()
template<typename Lhs, typename Rhs>
auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {
auto tmp = l.value + r.value;
if constexpr (sizeof(l.value) < sizeof(r.value)) // if constexpr here!
return Register<Rhs>{ tmp };
else
return Register<Lhs>{ tmp };
}
otherwise auto
deduction type doesn't works when sizeof(l.value)
is different from sizeof(r.value)
. 否则当
sizeof(l.value)
与sizeof(r.value)
不同时, auto
扣除类型不起作用。
(2) From your first version of operator()
(that works because you sum two value of the same type) you have an overflow. (2)从你的第一个
operator()
版本operator()
因为你将两个相同类型的值相加而起作用),你会有溢出。
More exactly: 更确切地说:
Lhs
and Rhs
are std::uint8_t
so the function return a Register<std::uint8_t>
. Lhs
和Rhs
是std::uint8_t
所以函数返回Register<std::uint8_t>
。 tmp
become std::uint32_t
(see point 3) but assigning it to a std::uint8_t
loose the overflow tmp
变为std::uint32_t
(参见第3点)但是将它分配给std::uint8_t
松散溢出 (3) From my platform, from the code (3)从我的平台,从代码
std::cout << sizeof(char) << std::endl;
std::cout << sizeof(std::declval<char>()+std::declval<char>()) << std::endl;
std::cout << sizeof(short) << std::endl;
std::cout << sizeof(std::declval<short>()+std::declval<short>()) << std::endl;
I get 我明白了
1
4
2
4
It's called "integral promotion". 它被称为“整体推广”。
In short: the sum of two char
become a int
; 简而言之:两个
char
的总和成为一个int
; the sum of two short
become a int
. 两个
short
的总和成为一个int
。
This should clarify why works (but not exactly as you want, I suppose) your second version of operator+ ()
这应该澄清为什么有效(但不完全是你想要的,我想)你的第二个版本的
operator+ ()
template<typename Lhs, typename Rhs>
auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {
return Register<decltype(l.value + r.value)>{ l.value + r.value };
}
You have that decltype(l.value + r.value)
is int
; 你有那个
decltype(l.value + r.value)
是int
; so that decltype(R8+R8)
is a R32
. 因此,
decltype(R8+R8)
是R32
。
What are Rhs
and Lhs
when 什么是
Rhs
和Lhs
r8 + r8
is called? 叫做? Both are
uint8_t
, so in first version 两者都是
uint8_t
,所以在第一个版本中
auto tmp = l.value + r.value;
if (sizeof(l.value) < sizeof(r.value))
return Register<Rhs>{ tmp };
else
return Register<Lhs>{ tmp };
temporary tmp = 478
is passed to Register
whose value
type is uint8_t
, and you lose data. 临时
tmp = 478
传递给其value
类型为uint8_t
Register
,并丢失数据。
In second version 在第二个版本
return Register<decltype(l.value + r.value)>{ l.value + r.value };
you are using decltype
to get type of l.value + r.value
. 您正在使用
decltype
来获取l.value + r.value
类型。 Both types are uint_8
, but while performing integer operation both are promoted to int
, so decltype()
returns int
, width of int
is enough to store 478. 这两种类型的
uint_8
,但在执行整数操作二者都提升到int
,如此decltype()
返回int
,的宽度int
足以存储478。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.