多拷贝构造函数继承中令人惊讶的行为

Dan*_*n R 3 c++ inheritance constructor using-declaration c++11

从C++ 11开始,可以有两个复制构造函数,一个采用类型参数,另一个采用类型T&参数const T&.

我有一种情况,即(似乎)添加第二个复制构造函数会导致在构造函数在派生类中继承时不会调用任何一个.当两者都存在时,复制构造函数被模板化构造函数覆盖.

这是一个MWE:

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};

struct C :public A { using A::A; };

int main() {
  C c1;
  C c2(c1);
}
Run Code Online (Sandbox Code Playgroud)

运行此代码,我们看到输出

non-default ctor called
copy ctor from non-const ref
Run Code Online (Sandbox Code Playgroud)

这是预期的.

但是,添加一个额外的构造函数struct A如下:

  A (const A&) { }
Run Code Online (Sandbox Code Playgroud)

以某种方式导致其他复制构造函数不被调用,因此输出变为

non-default ctor called
non-default ctor called
Run Code Online (Sandbox Code Playgroud)

在我的用例中,我想将所有构造函数从基类继承到派生类,包括复制构造函数和其他任何东西.但似乎不知何故,当两个拷贝构造函数都存在时,它们不会被继承.这里发生了什么?

Jar*_*d42 6

来自https://en.cppreference.com/w/cpp/language/using_declaration

如果Base的一个继承构造函数恰好具有与Derived的复制/移动构造函数匹配的签名,则它不会阻止派生复制/移动构造函数的隐式生成(然后隐藏继承版本,类似于使用operator =) .

所以

struct C :public A { using A::A; };
Run Code Online (Sandbox Code Playgroud)

struct C :public A
{
    using A::A;
    C(const C&) = default;
    C(C&&) = default;
};
Run Code Online (Sandbox Code Playgroud)

在哪里C(const C&) = default;类似

C(const C& c) : A(static_cast<const A&>(c)) {}
Run Code Online (Sandbox Code Playgroud)

所以

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
Run Code Online (Sandbox Code Playgroud)

选择模板构造函数,但是

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (const A&) { std::cout << "copy ctor from const ref\n"; }
  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
Run Code Online (Sandbox Code Playgroud)

A (const A&) 被选中.

您可以注意到,还有一个缺陷:

继承构造函数的语义由针对C++ 11的缺陷报告追溯更改.以前,继承构造函数声明会导致一组合成的构造函数声明被注入到派生类中,从而导致冗余的参数复制/移动,与某些形式的SFINAE存在有问题的交互,并且在某些情况下可能无法在主要ABI上实现.较旧的编译器仍然可以实现先前的语义.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0136r1.html

有了这个缺点,你的C级就是

struct C :public A
{
    using A::A;

    template <typename ...Ts>
    C(Ts&&... ts) : A(std::forward<Ts>(ts)...) {} // Inherited.

    C(const C&) = default;
    C(C&&) = default;
};
Run Code Online (Sandbox Code Playgroud)

所以你打电话C(C& c) : A(c) {}(模板替换后).