需要明确在类模板中为朋友算子+自动返回类型的推论

时间:2019-07-14 07:30:19

标签: c++ templates operator-overloading c++17 auto

我只是想在这里弄清楚这段代码发生了什么,以及为什么这样做:

main.cpp

string mathFunc = "Round";
MethodInfo method = typeof(Math).GetMethod(mathFunc, new[] {typeof(double) });
Console.WriteLine(method.Invoke(null, new object [] { 1.78d }));

我的主函数中的代码不变,因为两种情况都相同:


这是我正在工作的#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; } ,这是我的第一次尝试:

operator+

这是程序的输出:

输出v1

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 };
}

如上所示,期望值应为Reg16(478) Prev: 0 hex: 0x01DE bin: 0000000111011110 Reg16(222) Prev: 0 hex: 0x00DE bin: 0000000011011110 Reg16(222) Prev: 0 hex: 0x00DE bin: 0000000011011110 (十进制)或478(十六进制)。但是,在这种情况下,0x01DEoperator=构造函数无法从Register<T>获得适当的值。


通过将operator+更改为此,我可以解决此问题:

operator+

这给了我正确的结果:

输出v2

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 };
}

如果您需要查看我的完整类的实现,可以在我的问题(关注)下方找到它:我正在这里寻求清晰性和对行为的更好理解。

我想知道的是为什么第一个版本没有产生正确或期望的值,以及为什么第二个尝试却没有产生正确或期望的值。两种实现之间的主要区别是什么,以及编译器内部发生了什么?我正在使用Visual Studio 2017。


注册。h

Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110

Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110

Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110

2 个答案:

答案 0 :(得分:2)

三点。

(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 };
}

否则,当autosizeof(l.value)不同时,sizeof(r.value)推导类型将不起作用。

(2)从operator()的第一个版本开始(之所以有效,是因为您将两个相同类型的值相加),所以出现了溢出。

更确切地说:

  • LhsRhsstd::uint8_t,因此该函数返回Register<std::uint8_t>
  • tmp成为std::uint32_t(请参阅第3点),但将其分配给std::uint8_t可以消除溢出

(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;

我明白了

1
4
2
4

这称为“整体促销”。

简而言之:两个char之和成为int;两个short之和成为int

这应该说明为什么您的第二版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 };
}

您认为decltype(l.value + r.value)int;因此decltype(R8+R8)R32

答案 1 :(得分:1)

什么是RhsLhs

r8 + r8

被称为?两者均为uint8_t,因此在第一版

auto tmp = l.value + r.value;
if (sizeof(l.value) < sizeof(r.value))
    return Register<Rhs>{ tmp };
else
    return Register<Lhs>{ tmp };

临时tmp = 478传递给Register类型为value的{​​{1}},并且您会丢失数据。


第二版

uint8_t

您正在使用return Register<decltype(l.value + r.value)>{ l.value + r.value }; 来获取decltype的类型。两种类型均为l.value + r.value,但是在执行整数运算时,它们都被提升为uint_8,因此int返回decltype()int的宽度足以存储478。