Вопросы о трех конструкторах в стандартном интерфейсе std:: variant

Почему конструктор (4) существует для std::variant из http://en.cppreference.com/w/cpp/utility/variant/variant? Похоже, что это вызовет большую двусмысленность в коде, которого в противном случае можно было бы избежать, будучи явным. Например, пример кода в cppreference подчеркивает возможную двусмысленность, которую пользователи могут не заметить (третья строка)

variant<string> v("abc"); // OK
variant<string, string> w("abc"); // ill-formed, can't select the alternative to convert to
variant<string, bool> w("abc"); // OK, but chooses bool

Есть ли какой-нибудь случай, когда это абсолютно необходимо?

Другой вопрос заключался в том, почему конструкторы (6) и (8) нужны на той же странице cppreference. Не будут ли (5) и (7) служить целям, для которых предназначены (6) и (8)? Возможно, я ошибаюсь в их использовании.


Для читателя конструкторы, о которых я упоминал в моем вопросе,

constexpr variant();              // (1)    (since C++17)

variant(const variant& other);    // (2)    (since C++17)

variant(variant&& other);         // (3)    (since C++17)

template< class T >               // (4)    (since C++17)
constexpr variant(T&& t);

template< class T, class... Args >
constexpr explicit variant(std::in_place_type_t<T>, Args&&... args); // (5) (since C++17)

template< class T, class U, class... Args >
constexpr explicit variant(std::in_place_type_t<T>,
                           std::initializer_list<U> il, Args&&... args); // (6) (since C++17)

template< std::size_t I, class... Args >
constexpr explicit variant(std::in_place_index_t<I>, Args&&... args) // (7) (since C++17)

template <size_t I, class U, class... Args>
constexpr explicit variant(std::in_place_index_t<I>,
                           std::initializer_list<U> il, Args&&... args); // (8) (since C++17)

Ответ 1

Есть ли какой-нибудь случай, когда это абсолютно необходимо?

Нет. Но вещи не добавляются, потому что они "абсолютно понадобятся". Они добавляются, потому что они полезны.

И неявно конвертируемый из одного из его типов компонентов очень полезен для variant. Да, это создает двусмысленность в некоторых случаях. Но эта неоднозначность обычно связана с дефектами в дизайне типов (например, строковые литералы, предпочитающие преобразовывать в bool по пользовательским преобразованиям).

Если существует двусмысленный случай, тогда вам просто нужно быть явным. Как использование "abc"s литералов UDL, а не голых строковых литералов (еще одна причина для этого). Но нет причин заставить всех быть явным, когда вы имеете дело с хорошо продуманными типами.

Не будет (5) и (7) служить целям, для которых (6) и (8) предназначены для?

Не разумным способом.

В каждом случае в стандарте, когда функция принимает переменные аргументы, которые будут переданы конструктору, они будут использовать синтаксис конструктора, а не синтаксис {} для этого объекта. Поэтому, если у вас есть это:

using type = vector<int>;
variant<type> t(in_place<type>, 6);

Вы получите звонок vector<int>(6). Обратите внимание, что это отличается от vector<int>{6}. То есть вы не получаете конструкторы списка инициализаторов, если вы фактически не передаете список инициализаторов.

Теперь вы можете сделать:

variant<type> t(in_place<type>, initializer_list<int>{6});

Но это слишком многословно. Напротив:

variant<type> t(in_place<type>, {6});

Это гораздо менее многословно. Компилятор может вывести тип списка инициализаторов. В то время как вывод типа аргумента шаблона не получается, если вы попытаетесь вывести braced-init-list как произвольное T.

Среди других способов вычитание шаблона отличается от вычета с помощью auto, потому что оно не выводит выражения initializer_list из braced-init-list. Например

template <typename Type>
void func(const Type&);

не выводит Type как std::initializer_list для следующего вызова

func({1, 2, 3, 4, 5});

для получения дополнительной информации об этом см. Универсальные ссылки и std:: initializer_list.