为什么模板构造函数优于复制构造函数?
Why is template constructor preferred to copy constructor?
#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?
}
构造函数的模板重载适用于两种声明(u2
和 u3
)。但是当 int
传递给构造函数时,选择了非模板重载。调用复制构造函数时,会选择模板重载。据我所知,在重载解析期间,非模板函数总是优先于模板函数。为什么复制构造函数的处理方式不同?
As far as I know non-template function is always preferred to template function during overload resolution.
只有当特化和非模板完全一样的时候才会这样。但是这里不是这种情况。当您调用 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; }
问题是模板构造函数没有限定符 const
而非模板复制构造函数的参数中有限定符 const。如果将对象 u1
声明为 const
对象,则将调用非模板复制构造函数。
来自 C++ 标准(7 个标准转换)
1 Standard conversions are implicit conversions with built-in meaning.
Clause 7 enumerates the full set of such conversions. A standard
conversion sequence is a sequence of standard conversions in the
following order:
(1.4) — Zero or one qualification conversion
所以复制构造函数需要一个标准的转换,而模板构造函数不需要这样的转换。
When copy constructor is tried to be called, template overload is
chosen. As far as I know non-template function is always preferred to
template function during overload resolution. Why is copy constructor
handled differently?
template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
// ^^
选择模板版本的原因是因为编译器能够
生成一个带有签名 (T &)
的构造函数,它更适合因此被选中。
如果您将签名从 uct u1
更改为 const uct u1
那么它将适合复制构造函数(因为 u1
一开始就不是 const)。
如果您将签名从 uct(const uct &)
更改为 uct(uct&)
,它会更合适并且会选择模板版本。
此外,如果您使用 uct u3(std::move(u1));
,则会选择 uct(uct&&)
要解决此问题,您可以使用 SFINAE 在 T
与 uct
相同时禁用重载:
template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, uct>>>
uct(T&&)
{
std::cerr << "template" << std::endl;
}
#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?
}
构造函数的模板重载适用于两种声明(u2
和 u3
)。但是当 int
传递给构造函数时,选择了非模板重载。调用复制构造函数时,会选择模板重载。据我所知,在重载解析期间,非模板函数总是优先于模板函数。为什么复制构造函数的处理方式不同?
As far as I know non-template function is always preferred to template function during overload resolution.
只有当特化和非模板完全一样的时候才会这样。但是这里不是这种情况。当您调用 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; }
问题是模板构造函数没有限定符 const
而非模板复制构造函数的参数中有限定符 const。如果将对象 u1
声明为 const
对象,则将调用非模板复制构造函数。
来自 C++ 标准(7 个标准转换)
1 Standard conversions are implicit conversions with built-in meaning. Clause 7 enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:
(1.4) — Zero or one qualification conversion
所以复制构造函数需要一个标准的转换,而模板构造函数不需要这样的转换。
When copy constructor is tried to be called, template overload is chosen. As far as I know non-template function is always preferred to template function during overload resolution. Why is copy constructor handled differently?
template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
// ^^
选择模板版本的原因是因为编译器能够
生成一个带有签名 (T &)
的构造函数,它更适合因此被选中。
如果您将签名从
uct u1
更改为const uct u1
那么它将适合复制构造函数(因为u1
一开始就不是 const)。如果您将签名从
uct(const uct &)
更改为uct(uct&)
,它会更合适并且会选择模板版本。此外,如果您使用
uct u3(std::move(u1));
,则会选择
uct(uct&&)
要解决此问题,您可以使用 SFINAE 在 T
与 uct
相同时禁用重载:
template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, uct>>>
uct(T&&)
{
std::cerr << "template" << std::endl;
}