为什么可变参数模板构造函数比复制构造函数更好?

kpx*_*894 10 c++ templates overload-resolution variadic-templates c++11

以下代码无法编译:

#include <iostream>
#include <utility>

struct Foo
{
    Foo() { std::cout << "Foo()" << std::endl; }
    Foo(int) { std::cout << "Foo(int)" << std::endl; }
};

template <typename T>
struct Bar
{
    Foo foo;

    Bar(const Bar&) { std::cout << "Bar(const Bar&)" << std::endl; }

    template <typename... Args>
    Bar(Args&&... args) : foo(std::forward<Args>(args)...)
    {
        std::cout << "Bar(Args&&... args)" << std::endl;
    }
};

int main()
{
    Bar<Foo> bar1{};
    Bar<Foo> bar2{bar1};
}
Run Code Online (Sandbox Code Playgroud)

编译器错误告诉我编译器试图使用variadic模板构造函数而不是复制构造函数:

prog.cpp: In instantiation of 'Bar<T>::Bar(Args&& ...) [with Args = {Bar<Foo>&}; T = Foo]':
prog.cpp:27:20:   required from here
prog.cpp:18:55: error: no matching function for call to 'Foo::Foo(Bar<Foo>&)'
  Bar(Args&&... args) : foo(std::forward<Args>(args)...)
Run Code Online (Sandbox Code Playgroud)

为什么编译器会这样做以及如何解决它?

Bar*_*rry 12

这个电话:

Bar<Foo> bar2{bar1};
Run Code Online (Sandbox Code Playgroud)

在其过载集中有两个候选者:

Bar(const Bar&);
Bar(Bar&);       // Args... = {Bar&}
Run Code Online (Sandbox Code Playgroud)

确定一个转换序列是否优于另一个转换序列的方法之一是来自[over.ics.rank]:

标准转换序列S1是比标准转换序列S2更好的转换序列

- [...]
- S1和S2是引用绑定(8.5.3),引用引用的类型除了顶级cv -qualifiers 之外是相同的类型,以及S2初始化引用的类型refer比使用S1初始化的引用所引用的类型更加cv- qualified.[例如:

int f(const int &);
int f(int &);
int g(const int &);
int g(int);

int i;
int j = f(i);    // calls f(int &)
int k = g(i);    // ambiguous
Run Code Online (Sandbox Code Playgroud)

- 末端的例子]

转发引用可变参数构造函数是一个更好的匹配,因为它的引用binding(Bar&)比复制构造函数的引用binding()更少cv资格const Bar&.

就解决方案而言,您可以随时从候选集中排除Args...您应该使用SFINAE调用副本或移动构造函数的内容:

template <typename... > struct typelist;

template <typename... Args,
          typename = std::enable_if_t<
              !std::is_same<typelist<Bar>,
                            typelist<std::decay_t<Args>...>>::value
          >>
Bar(Args&&... args)
Run Code Online (Sandbox Code Playgroud)

如果Args...是一Bar,Bar&,Bar&&,const Bar&,那么typelist<decay_t<Args>...>将是typelist<Bar>-这就是我们要排除的情况下.任何其他一组Args...将被允许​​就好了.

  • @ kpx1894不,C++ 11版本首先在你自己的命名空间中编写`decay_t`和`enable_if_t`,然后用它代替`std ::`版本. (2认同)

Jay*_*ler 6

虽然我同意这是违反直觉的,但原因是你的复制构造函数需要一个const Bar&但bar1不是const.

http://coliru.stacked-crooked.com/a/2622b4871d6407da

由于通用引用可以绑定任何内容,因此在const限制性构造函数中使用const要求.