模板构造函数优先于普通复制和移动构造函数

Template Constructor Taking Precedence Over Normal Copy and Move Constructor?

本文关键字:构造函数 复制 移动 优先于      更新时间:2023-10-16

下面程序的输出…

#include <iostream>
using namespace std;
struct X
{
    X(const X&)              { cout << "copy" << endl; }
    X(X&&)                   { cout << "move" << endl; }
    template<class T> X(T&&) { cout << "tmpl" << endl; }
};
int main()
{
    X x1 = 42;
    X x2(x1);
}

tmpl
tmpl

期望的输出是:

tmpl
copy

为什么具体复制构造函数没有优先于模板构造函数?

是否有办法修复它,以便复制和移动构造函数重载将优先于模板构造函数?

嗯,这是因为引用崩溃

在重载解析阶段,当函数模板实例化时,T被扣除为X&,因此T&&(即X& &&)由于引用坍缩而成为X&,并且从函数模板实例化的函数变成精确匹配,复制构造函数需要X&转换到const X&(这就是为什么它没有被选择为劣质匹配)。

但是,如果从复制构造函数中删除const,则首选复制构造函数。试试这个:

X(/*const*/ X&) { cout << "copy" << endl; }

输出符合预期。

或者,如果您将函数模板中的参数设置为const T&,则将调用复制构造函数(即使它保持不变!),因为现在不会出现引用崩溃:

template<class T> X(const T &) { cout << "tmpl" << endl; }

再次输出

如果您不想添加另一个构造函数(如其他答案所建议的),您可以使用SFINAE来约束调用,通过以下方式替换您的模板构造函数:

template<class T
    , typename std::enable_if<not std::is_same<X, typename std::decay<T>::type>::value, int>::type = 0 
> X(T&&) { cout << "tmpl " << endl; }

只需要添加一个dummy默认模板参数(一种已知的技术:Link)。不需要额外的头文件。

您将得到所需的输出。

我从一个相关的问题中得到了这个答案:链接。所有这些看起来相当不优雅,但似乎是目前唯一的出路。我仍然希望看到一个更优雅的解决方案

正常的重载解析规则在选择构造函数时仍然适用,并且构造函数使用非const左值引用(对于参数推导后的模板构造函数)比使用const左值引用的构造函数更匹配。

当然,您可以添加另一个接受非const左值引用的重载,即

X(X&)              { cout << "copy" << endl; }

Update:其他模板构造函数更匹配的情况:

const X f()
{ return X(); }
struct Y : X
{ Y() { } };
int main()
{
  X x3(f()); // const-qualified rvalue
  Y y;
  X x4(y); // derived class
}