为啥模板构造函数优于复制构造函数?

Posted

技术标签:

【中文标题】为啥模板构造函数优于复制构造函数?【英文标题】:Why is template constructor preferred to copy constructor?为什么模板构造函数优于复制构造函数? 【发布时间】:2020-01-14 12:30:15 【问题描述】:
#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?

coliru

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

【问题讨论】:

“太完美的转发”。 akrzemi1.wordpress.com/2013/10/10/too-perfect-forwarding 本题中基于意见的内容为零。为什么将 VTC 作为 POB? 【参考方案1】:

据我所知,在重载决议期间,非模板函数总是优于模板函数。

这是真的,只有当特化和非模板完全一样时。但这里不是这种情况。当你调用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; 

【讨论】:

只是补充一下,这就是为什么模板化的构造函数是奇怪的野兽,除非你想让它们超越一切,否则你可能想要标记构造函数。 (类似于inplace_t @SergeyA 或使用 SFINAE。 或者那样,但标记通常更容易 添加const 将前向引用转换为右值引用。因此它不是首选,因为非模板与模板规则,但因为 uct u3(u1) 根本不匹配 重载 any 函数以获取转发引用是令人担忧的。【参考方案2】:

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

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

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

如果您将签名从 uct u1 更改为 const uct u1,那么它将适合复制构造函数(因为 u1 不是以 const 开头)。

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

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


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

template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, uct>>>
uct(T&&)

  std::cerr << "template" << std::endl;

【讨论】:

【参考方案3】:

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

来自 C++ 标准(7 种标准转换)

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

(1.4) — 零或一次资格转换

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

【讨论】:

以上是关于为啥模板构造函数优于复制构造函数?的主要内容,如果未能解决你的问题,请参考以下文章

为啥隐式复制构造函数调用基类复制构造函数而定义的复制构造函数不调用?

为啥我们需要复制构造函数以及何时应该在 java 中使用复制构造函数

如果类具有参数化构造函数,为啥Java不提供默认构造函数? [复制]

为啥在我的代码中调用复制构造函数而不是移动构造函数?

为啥当类包含任何参数化构造函数时编译器不提供默认构造函数? [复制]

为啥在malloc中不调用构造函数? [复制]