template <class T, class U> decltype(*(T*)(0) * *(U*)(0)) mul(T x, U y) {
return x * y;
}
这段代码来自Stroustrup的C++11 FAQ。我理解它的作用,它是两个不同类型的对象相乘。令我困惑的是模板参数和函数定义之间的语法。 decltype
内发生了什么?我认为它取消引用一个初始化为0的未命名T
指针,并将其与一个未命名的U
指针相乘,并以相同的方式进行解引用和初始化。我是对的吗?
好吧,如果这是正在发生的事情,那么指针,解引用和额外括号的使用是不是多余的?我不能在保持所需效果的同时初始化这样的类型吗?:
template <class T, class U> decltype(T(0) * U(0)) mul(T x, U y) {
return x * y;
}
这对我来说看起来更干净,而 在乘以两个数字时会产生相同的效果...
mul(4, 3); // 12
那么为什么Stroustrup坚持使用复杂的指针,取消引用和初始化语法?当然,这是在他引入新的auto
语法之前。但无论如何,我的问题是:上述两种形式的初始化之间有什么区别吗?他在哪里使用指针并立即取消引用它们而不是简单地做我所做的,这是用没有指针或解除引用来初始化类型?任何回应都表示赞赏。
答案 0 :(得分:6)
您的版本不等于。
T
和U
都可以从0
构建。从矩阵中预期这一点显然是错误的,但它们可以成倍增加。T(0)
产生一个临时的(可能绑定到T&&
),而*(T*(0))
产生对现有对象的引用(即T&
),因此是一个不同的运算符可能会被选中。然而,Stroustrup的版本和你的版本最终都没有在实践中使用 。在相同级别的编译器实现中,可以使用:
template <typename T, typename U>
decltype(std::declval<T>() * std::declval<U>()) mul(T x, U y);
但是它未能利用延迟返回类型规范,该规范是为了允许在函数的参数声明之后推迟返回类型的声明:auto f(int, int) -> int
。一旦声明了参数,它们就可以使用,这对decltype
非常有用!
template <typename T, typename U>
auto mul(T x, U y) -> decltype(x * y);
后一种形式保证选择与函数体相同的运算符重载,但代价是重复(不幸的是)。
答案 1 :(得分:5)
您的代码版本假设T和U具有默认构造函数。 Stroustrup版本没有,它通过取消引用空指针来创建一个虚拟对象。当然,这并不重要,因为该代码并不意味着执行,只是要解析它以了解结果类型。
答案 2 :(得分:4)
decltype
内容是未评估的背景;什么是无关紧要的,只要它有一个类型。考虑一下T
定义如下:
struct T
{
int operator*(const U &) { return 2; }
};
它没有构造函数使用int
或int
可转换为的任何类型;因此,即使在T(0)
的未评估上下文中,decltype
也不会产生对象。因此,使用未评估的空引用可能是获得正确类型的最简单方法。
结论:你不知道T
和U
有什么构造函数,所以你应该使用引用正确类型的虚拟对象的null引用。