Я просто в баре с Ричардом Корденом, и между нами мы пришли к выводу, что проблема не имеет ничего общего с переменными или значениями. Неявно сгенерированная конструкция копирования в этом случае принимает MyBase const&
в качестве аргумента. Шаблонный конструктор выводит тип аргумента как MyBase&
. Это лучшее совпадение, которое вызывается, хотя это не конструктор копирования.
Пример кода, который я использовал для тестирования, таков:
#include <utility>
#include <vector>i
template <typename T>
struct MyBase
{
template <typename... S> MyBase(S&&... args):
m(std::forward<S>(args)...)
{
}
T m;
};
struct Derived: MyBase<std::vector<int> >
{
};
int main()
{
std::vector<int> vec(3, 1);
MyBase<std::vector<int> > const fv1{ vec };
MyBase<std::vector<int> > fv2{ fv1 };
MyBase<std::vector<int> > fv3{ fv2 }; // ERROR!
Derived d0;
Derived d1(d0);
}
Мне нужно было отказаться от использования списков инициализаторов, потому что это еще не поддерживается clang. Этот пример компилируется, за исключением инициализации fv3
, которая завершается неудачно: конструктор копирования, синтезированный для MyBase<T>
, принимает MyBase<T> const&
и, таким образом, передача fv2
вызывает конструктор variadic, перенаправляющий объект в базовый класс.
Возможно, я неправильно понял вопрос, но на основании d0
и d1
кажется, что синтезируются как конструктор по умолчанию, так и конструктор копирования. Тем не менее, это с довольно актуальными версиями gcc и clang. То есть, это не объясняет, почему конструктор копирования не синтезируется, потому что он синтезирован.
Чтобы подчеркнуть, что эта проблема не имеет ничего общего с переменными списками аргументов или значениями: в следующем коде показана проблема с вызовом шаблонного конструктора, хотя выглядит так, как будто вызывается конструктор копирования, а конструкторы копирования никогда не являются шаблонами. Это на самом деле несколько удивительное поведение, о котором я точно не знал:
#include <iostream>
struct MyBase
{
MyBase() {}
template <typename T> MyBase(T&) { std::cout << "template\n"; }
};
int main()
{
MyBase f0;
MyBase f1(const_cast<MyBase const&>(f0));
MyBase f2(f0);
}
В результате добавление конструктора с переменными параметрами, как в вопросе, к классу, у которого нет других конструкторов, изменяет работу конструкторов копирования поведения! Лично я считаю, что это довольно неудачно. Это фактически означает, что класс MyBase
должен быть дополнен также конструкторами копирования и перемещения:
MyBase(MyBase const&) = default;
MyBase(MyBase&) = default;
MyBase(MyBase&&) = default;
К сожалению, это не работает с gcc: он жалуется на конструкторы копий по умолчанию (он утверждает, что конструктор дефолтных копий, принимающий неконстантную ссылку, не может быть определен в определении класса). Clang принимает этот код без каких-либо жалоб. Использование определения конструктора копирования с использованием неконстантной ссылки работает как с gcc, так и clang:
template <typename T> MyBase<T>::MyBase(MyBase<T>&) = default;