为什么模板构造函数比复制构造函数更受青睐?

时间:2019-09-12 15:23:18

标签: c++ copy-constructor overload-resolution function-templates constructor-overloading

#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传递给构造函数时,将选择非模板重载。调用复制构造函数时,将选择模板重载。据我所知,在重载解析期间,非模板函数总是比模板函数更受青睐。为什么复制构造函数的处理方式不同?

3 个答案:

答案 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开头)。

    < / li>
  • 如果将签名从uct(const uct &)更改为uct(uct&),将是一个更好的选择,它将选择模板版本。

  • 此外,如果您使用过uct(uct&&)

  • ,则会选择uct u3(std::move(u1));

要解决此问题,可以在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;
}

答案 2 :(得分:2)

问题在于模板构造函数没有限定条件const,而非模板副本构造函数的参数中具有限定符const。如果将对象u1声明为const对象,则将调用非模板副本构造函数。

来自C ++ STandard(7个标准转换)

  

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

     

(1.4)-零或一项资格转换

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