我正在研究自C ++ 17以来可用的类模板推导。以下是我想问的代码:
#include <iostream>
#include <cmath>
using std::endl;
using std::cout;
template<typename T>
struct MyAbs {
template<typename U>
MyAbs(U&& u) : t(std::forward<T>(u))
{ cout << "template" << endl;}
#ifdef ON
MyAbs(const T& t) : t(t) {}
#endif
T operator()() const
{
return std::abs(t);
}
T t;
};
/*
// may need the following
template<typename U>
MyAbs(U&&) -> MyAbs<typename std::remove_reference<U>::type>;
*/
int main()
{
const double d = 3.14;
cout << MyAbs(4.7)() << endl;
cout << MyAbs(d)() << endl;
return 0;
}
当MyAbs(const T&)
没有条件编译时(即没有-DON
),clang ++和g ++都无法推导出模板参数T
。鉴于-DON=1
,两个编译器都构建了上面的简单示例。
首先,我还猜测扣除应该失败;编译器可以推导U
但不推导T
。我得到的编译错误是我的预期。如果我弄错了,请告诉我。
如果我对它是正确的,那么,当我添加U&&
时,我无法理解为什么MyAbs(const T&)
的扣除成功。我期望用U&&
推断失败,SFINAE允许我调用MyAbs(const T&)
而不是两种情况:4.7和d。然而,发生的事情是不同的。该程序似乎调用了4.7的模板版本和d
的非模板版本。
$ g++ -Wall -std=c++17 ~/a.cc -DON=1
$ ./a.out
template
4.7
3.14
似乎模板版本突然变得可行。这是预期的吗?如果是这样,那是什么原因?
答案 0 :(得分:21)
类模板参数推导分两个阶段进行。
步骤1仅帮助您找出类模板参数。它对于可能在步骤2中使用的实际构造函数(或类模板特化)没有任何作用。
构造函数的存在可能会推动推论,但是如果使用给定的构造函数来推断出它是否用于构造它没有任何说明。
所以,当你刚才:
template<typename T>
struct MyAbs {
template<typename U>
MyAbs(U&& u);
};
类模板参数推导失败 - 您的构造函数中没有推导指南,T
是非推导的上下文。编译器无法在T
或MyAbs(4.7)
中找出您想要的MyAbs(d)
。
当你添加这个时:
template<typename T>
struct MyAbs {
template<typename U>
MyAbs(U&& u);
MyAbs(T const&);
};
现在可以!在这两种情况下,它都会将T
推导为double
。一旦它这样做,那么我们继续执行重载解析,就像我们开始输入MyAbs<double>
一样。
在这里,MyAbs<double>(4.7)
恰好喜欢转发引用构造函数(较少cv限定的引用),而MyAbs<double>(d)
恰好优先于另一个(非模板首选模板)。这是好的和预期的,因为我们使用一个构造函数进行推导并不意味着我们必须专门使用构造函数来构造。