Конфликт между идеальным конструктором пересылки и конструктором копирования в иерархии классов

Недавно я столкнулся с проблемой при попытке реализовать иерархию классов с идеальными конструкторами пересылки.
Рассмотрим следующий пример:

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&& соответственно).

7

Решение

Попробуйте изменить 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)?

2

Другие решения

Давайте подробнее рассмотрим сообщение об ошибке.

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,

1

Следующее должно работать, и оно не использует явные приведения:

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)) {}
};
1
По вопросам рекламы [email protected]