编译器如何使用转发引用推断出此类模板?

时间:2018-04-03 16:43:13

标签: c++ templates c++17 template-deduction

我正在研究自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

似乎模板版本突然变得可行。这是预期的吗?如果是这样,那是什么原因?

1 个答案:

答案 0 :(得分:21)

类模板参数推导分两个阶段进行。

  1. 推断类模板参数。
  2. 然后,用具体类类型进行实际构造。
  3. 步骤1仅帮助您找出类模板参数。它对于可能在步骤2中使用的实际构造函数(或类模板特化)没有任何作用。

    构造函数的存在可能会推动推论,但是如果使用给定的构造函数来推断出它是否用于构造它没有任何说明。

    所以,当你刚才:

    template<typename T>
    struct MyAbs {
         template<typename U>
         MyAbs(U&& u);
    };
    

    类模板参数推导失败 - 您的构造函数中没有推导指南,T是非推导的上下文。编译器无法在TMyAbs(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)恰好优先于另一个(非模板首选模板)。这是好的和预期的,因为我们使用一个构造函数进行推导并不意味着我们必须专门使用构造函数来构造。