#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
传递给构造函数时,将选择非模板重载。调用复制构造函数时,将选择模板重载。据我所知,在重载解析期间,非模板函数总是比模板函数更受青睐。为什么复制构造函数的处理方式不同?
答案 0 :(得分:33)
据我所知,在重载解决方案中,非模板功能总是比模板功能更受欢迎。
仅当专业化和非模板完全相同时,这是正确的。但是这里不是这种情况。当您调用List<List<Integer>> result = new ArrayList();
public void getPath(TreeNode root, List<Integer> path) {
if(root == null)
return;
path.add(root.val);
if(root.left == null && root.right == null) {
this.result.add(path);
return;
}
else {
getPath(root.left, new ArrayList(path));
getPath(root.right, new ArrayList(path));
//getPath(root.left, path);
//getPath(root.right, path);
}
}
时,过载集将得到
uct u3(u1)
现在,由于uct(const uct &)
uct(uct &) // from the template
不是const,因此必须应用const转换来调用复制构造函数。要调用模板专业化,它是完全匹配的,因此无需执行任何操作。这意味着模板将胜出,因为它是更好的匹配。
要停止此操作,您可以使用SFINAE来限制仅在u1
不是T
时才调用模板函数。看起来像
uct
答案 1 :(得分:4)
尝试调用复制构造函数时,模板重载为 选择。据我所知,非模板功能始终是首选 解析过程中的模板函数。为什么是复制构造函数 处理不同?
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));
要解决此问题,可以在T
与uct
相同时使用SFINAE禁用重载:
template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, uct>>>
uct(T&&)
{
std::cerr << "template" << std::endl;
}
答案 2 :(得分:2)
问题在于模板构造函数没有限定条件const
,而非模板副本构造函数的参数中具有限定符const。如果将对象u1
声明为const
对象,则将调用非模板副本构造函数。
来自C ++ STandard(7个标准转换)
1标准转换是具有内置含义的隐式转换。 第7条列举了全部此类转换。一个标准 转化顺序是指 以下顺序:
(1.4)-零或一项资格转换
因此,复制构造函数需要一种标准转换,而模板构造函数则不需要这种转换。