为什么模板构造函数比复制构造函数更受欢迎?

Why is template constructor preferred to copy constructor?

本文关键字:构造函数 受欢迎 复制 为什么      更新时间:2023-10-16
#include <iostream>
struct uct
{
uct() { std::cerr << "default" << std::endl; }
uct(const uct &) { std::cerr << "copy" << std::endl; }
uct(      uct&&) { std::cerr << "move" << std::endl; }
uct(const int  &) { std::cerr << "int" << std::endl; }
uct(      int &&) { std::cerr << "int" << std::endl; }
template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
};
int main()
{
uct u1    ; // default
uct u2( 5); // int
uct u3(u1); // template, why?
}

科里鲁

构造函数的模板重载适合两个声明(u2u3(。但是,当int传递给构造函数时,会选择非模板重载。调用复制构造函数时,将选择模板重载。据我所知,在重载解析期间,非模板函数始终优于模板函数。为什么复制构造函数的处理方式不同?

据我所知,在重载解析期间,非模板函数始终优于模板函数。

只有当专业化和非模板完全相同时,才是正确的。 但这里的情况并非如此。 当您调用uct u3(u1)时,过载集得到

uct(const uct &)
uct(uct &) // from the template

现在,由于u1不是 const,因此必须应用 const 转换来调用复制构造函数。 要调用模板专用化,它不需要执行任何操作,因为它是完全匹配的。 这意味着模板获胜,因为它是更好的匹配。

要停止这一点,您可以做的是使用 SFINAE 将模板函数限制为仅在T不是uct时调用。 那看起来像

template <typename T, std::enable_if_t<!std::is_same_v<uct, std::decay_t<T>>, bool> = true>
uct(T &&) { std::cerr << "template" << std::endl; }

当尝试调用复制构造函数时,模板重载为 选择。据我所知,非模板函数总是首选 重载解析期间的模板函数。为什么是复制构造函数 处理方式不同?

template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
//    ^^

选择模板化版本的原因是编译器能够
生成一个具有签名(T &)的构造函数,该构造函数更适合,因此被选中。

  • 如果将签名从uct u1更改为const uct u1,那么它将适合复制构造函数(因为u1一开始就不是 const(。

  • 如果将签名从uct(const uct &)更改为uct(uct&)则签名会更合适,它会选择该签名而不是模板化版本。

  • 此外,如果您使用了uct(uct&&),则会选择uct u3(std::move(u1));


要解决此问题,您可以使用 SFINAE 在Tuct相同时禁用重载:

template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, uct>>>
uct(T&&)
{
std::cerr << "template" << std::endl;
}

问题是模板构造函数没有限定const,而非模板复制构造函数在其参数中具有限定符 const。如果要将对象u1声明为const对象,则将调用非模板复制构造函数。

从C++标准(7 标准转换(

1 标准转换是具有内置含义的隐式转换。 第7条列举了此类转换的全部集合。一个标准 转换序列是标准转换序列 以下顺序:

(1.4( —零个或一个资格转换

因此,复制构造函数需要一个标准转换,而模板构造函数不需要这样的转换。