Недавно я столкнулся с проблемой при попытке реализовать иерархию классов с идеальными конструкторами пересылки.
Рассмотрим следующий пример:
struct TestBase {
template<typename T>
explicit TestBase(T&& t) : s(std::forward<T>(t)) {} // Compiler refers to this line in the error message
TestBase(const TestBase& other) : s(other.s) {}
std::string s;
};
struct Test : public TestBase {
template<typename T>
explicit Test(T&& t) : TestBase(std::forward<T>(t)) {}
Test(const Test& other) : TestBase(other) {}
};
Когда я пытаюсь скомпилировать код, я получаю следующую ошибку:
Ошибка 3 ошибка C2664: ‘std :: basic_string<_Elem, _Traits, _Alloc> :: basic_string (const std :: basic_string<_Elem, _Traits, _Alloc> &) ‘: невозможно преобразовать параметр 1 из’ const Test ‘в’ const std :: basic_string<_Elem, _Traits, _Alloc> &’
Насколько я понимаю, компилятор рассматривает идеальный конструктор пересылки как лучшую математику, чем конструктор копирования. Смотри например Скотт Мейерс: Копирование конструкторов в C ++ 11 . В других реализациях без иерархии классов я мог бы отключить конструктор идеальной пересылки от конструктора копирования через SFINAE. Смотри например Мартиньо Фернандес: некоторые подводные камни с конструкторами пересылки. Когда я пытаюсь применить упомянутое решение к этому примеру, я все еще не могу скомпилировать с тем же сообщением об ошибке.
Я думаю, что одним из возможных решений было бы избежать идеальной пересылки, взять параметры по значению в конструкторах и затем перейти от них к переменным класса.
Поэтому у меня вопрос: есть ли другие решения этой проблемы или в этом случае идеальная пересылка невозможна?
Обновить:
Оказалось, что мой вопрос легко понять неправильно. Поэтому я постараюсь немного разъяснить свои намерения и контекст.
const string&
а также string&&
соответственно).Попробуйте изменить Test(const Test& other) : TestBase(other) {}
в Test(const Test& other) : TestBase(static_cast<TestBase const&>(other)) {}
2-й Test
Конструктор вызывает TestBase, и есть две возможности. Один из них берет что-нибудь, другой берет TestBase. Но вы проходите тестирование к нему — «все» соответствует лучше. Путем явного приведения к константе TestBase&, мы должны быть в состоянии получить правильный выбор.
Другая возможность может заключаться в том, как устроен Test — может быть, то, что вы передали, совпало с конструктором шаблона вместо Test? Мы можем проверить эту другую возможность, удалив конструктор шаблона из Test и посмотрев, исчезла ли ошибка.
Если это так, то почему не сработала техника, которую вы связали (чтобы отключить конструктор шаблона Test, если тип, соответствующий типу Test)?
Давайте подробнее рассмотрим сообщение об ошибке.
std::basic_string<...>::basic_string(const std::basic_string<...> &) :
Это означает, что это относится к конструктору копирования std::string
cannot convert parameter 1 from 'const Test' to 'const std::basic_string<..> &
На самом деле, нет способа конвертировать из Test
в std::string
, Тем не мение, Test
имеет строковый член, а именно, std::string s;
,
Вывод: похоже, вы забыли добавить .s
в этом месте. Вероятно, это в s(std::forward<T>(t))
,
Другая возможная причина заключается в том, что 1-я перегрузка конструктора была выбрана вместо 2-й для создания копии экземпляра Test
,
Следующее должно работать, и оно не использует явные приведения:
struct Test : public TestBase {
private:
static TestBase const& toBase(const Test& o) { return o; }
public:
template <typename T>
explicit Test(T&& t) : TestBase(std::forward<T>(t)) {}
Test(const Test& other) : TestBase(toBase(other)) {}
};