Удивительное поведение при наследовании конструктора множественного копирования

Начиная с C++11, может быть два конструктора копирования, один из которых принимает параметр типа T&, а другой — параметр типа const T&.

У меня возникла ситуация, когда (по-видимому) добавление второго конструктора копирования не приводит к тому, что ни один из них не вызывается, когда конструкторы наследуются в производном классе. Конструктор копирования переопределяется шаблонным конструктором, если присутствуют оба.

Вот МВЕ:

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);
}

Запустив этот код, мы видим вывод

non-default ctor called
copy ctor from non-const ref

что, как и ожидалось.

Однако добавление дополнительного конструктора к struct A следующим образом:

  A (const A&) { }

каким-то образом другой конструктор копирования не вызывается, поэтому вывод становится

non-default ctor called
non-default ctor called

В моем случае я хочу наследовать все конструкторы базового класса в производный класс, включая конструкторы копирования и все остальное. Но кажется, что каким-то образом два конструктора копирования не наследуются, когда они оба присутствуют. Что здесь происходит?


person Dan R    schedule 04.07.2018    source источник
comment
Какой компилятор/флаги? После добавления A (const A&) { } я вижу только одну строку вывода: wandbox.org/permlink/ex8AyRHqF1ChsNcc (const Реф версия называется)   -  person jcai    schedule 05.07.2018
comment
Не уверен, но последние версии gcc и clang будут вызывать версию const-ref на wandbox.   -  person super    schedule 05.07.2018
comment
@Arcinde Спасибо за ссылку на wandbox! Кажется, что поведение было таким, как я описал до gcc 7 (я использовал gcc 5.4). Я так понимаю, это ошибка компилятора? Или это неопределенное поведение?   -  person Dan R    schedule 05.07.2018
comment
@super Похоже, что поведение изменилось с GCC 7 и clang 4.0. Я до сих пор не уверен, было ли это ошибкой (в обоих компиляторах) или неуказанным поведением.   -  person Dan R    schedule 05.07.2018
comment
Всегда можно было иметь A(A&) и A(A const&)   -  person M.M    schedule 05.07.2018
comment
Демонстрация, показывающая как ожидаемый, так и неожиданный результат в зависимости от версии компилятора.   -  person Jarod42    schedule 05.07.2018
comment
@M.M Мое замечание о том, что несколько конструкторов копирования являются чем-то новым в С++ 11, основано на документации здесь. Я думаю, что у вас всегда могут быть оба конструктора, но только с тех пор, как С++ 11 они оба считаются конструкторами копирования.   -  person Dan R    schedule 05.07.2018
comment
@DanRoche Я предполагаю, что раздел, на который вы ссылаетесь, должен указывать, что = default; был добавлен в C ++ 11. У вас все еще могут быть два пользовательских конструктора копирования в C++03. На самом деле в разделе 12.8/2 стандарта С++ 03 есть пример именно этого.   -  person M.M    schedule 05.07.2018


Ответы (1)


Из https://en.cppreference.com/w/cpp/language/using_declaration

Если один из унаследованных конструкторов Base имеет сигнатуру, которая соответствует конструктору копирования/перемещения Derived, это не препятствует неявной генерации конструктора копирования/перемещения Derived (который затем скрывает унаследованную версию, аналогично использованию оператора =). .

So

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

is

struct C :public A
{
    using A::A;
    C(const C&) = default;
    C(C&&) = default;
};

где C(const C&) = default; похоже на

C(const C& c) : A(static_cast<const A&>(c)) {}

Так с

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 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"; }
};

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;
};

Итак, вы вызываете C(C& c) : A(c) {} (после подстановки шаблона).

person Jarod42    schedule 04.07.2018
comment
Спасибо за полезную ссылку из документации. Но поведение прямо противоположно тому, что у вас здесь - конструктор шаблона выбирается только во втором случае, когда присутствуют оба ctor'а. (Кажется, это была ошибка компилятора, которая была исправлена ​​в более поздних версиях?) - person Dan R; 05.07.2018
comment
@DanRoche: тоже есть дефект. так что старые компиляторы все еще могут реализовать это. - person Jarod42; 05.07.2018