编译器如何通过转发引用推断出此 class 模板?

How can a compiler deduce this class template with forwarding reference?

我正在研究自 C++17 以来可用的 class 模板推导。下面是我想问的代码:

#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。我得到的编译错误是我所期望的。如果我弄错了,请告诉我。

如果我是对的,那么,我无法理解为什么在添加 MyAbs(const T&) 时用 U&& 推导成功。我期望的是 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

看来模板版突然变得可行了。这是预期的吗?如果有,是什么原因?

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

  1. 推导 class 模板参数。
  2. 然后,用混凝土 class 类型进行实际施工。

第 1 步仅帮助您找出 class 模板参数。对于可能在步骤 2 中使用的实际构造函数(或 class 模板特化),它没有做任何事情。

构造函数的存在可能会推动推论,但如果使用给定的构造函数进行推导,则与是否使用构造函数无关。


所以,当您拥有:

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

Class 模板参数推导失败 - 您的构造函数中没有推导指南,T 是一个 non-deduced 上下文。编译器无法在 MyAbs(4.7)MyAbs(d).

中弄清楚你想要什么 T

当你添加这个时:

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

     MyAbs(T const&);
};

现在可以了!在这两种情况下,它都将 T 推导为 double。一旦它这样做了,我们就继续执行重载解析,就好像我们一开始就输入了 MyAbs<double> 一样。

这里,MyAbs<double>(4.7) 恰好更喜欢转发引用构造函数(较少 cv-qualified 引用)而 MyAbs<double>(d) 恰好更喜欢另一个(non-template 更喜欢模板).这是可以预料的,仅仅因为我们使用一个构造函数进行推导并不意味着我们必须专门使用该构造函数进行构造。