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

Ste*_*hen 19 c++ templates template-argument-deduction c++17

我正在研究自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;
}
Run Code Online (Sandbox Code Playgroud)

如果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
Run Code Online (Sandbox Code Playgroud)

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

Bar*_*rry 21

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

  1. 推断类模板参数.
  2. 然后,使用具体类类型进行实际构造.

第1步只能帮助您找出类模板参数.它对于可能在步骤2中使用的实际构造函数(或类模板特化)没有任何作用.

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


所以,当你刚才:

template<typename T>
struct MyAbs {
     template<typename U>
     MyAbs(U&& u);
};
Run Code Online (Sandbox Code Playgroud)

类模板参数推导失败 - 您的构造函数中没有推导指南,T是一个非推导的上下文.编译器无法弄清楚T你想要什么MyAbs(4.7)MyAbs(d).

当你添加这个:

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

     MyAbs(T const&);
};
Run Code Online (Sandbox Code Playgroud)

现在它可以!在这两种情况下,它都推断Tdouble.一旦它这样做,那么我们继续执行重载解析,就像我们MyAbs<double>开始输入一样.

在这里,MyAbs<double>(4.7)碰巧更喜欢转发参考构造函数(较少cv限定的引用),而MyAbs<double>(d)碰巧更喜欢另一个(非模板首选模板).这是好的和预期的,因为我们使用一个构造函数进行推导并不意味着我们必须专门使用构造函数来构造.