Ответ 1
Вместо объяснения поведения компиляторов я попытаюсь объяснить, что говорит стандарт.
Основной пример
Для прямой инициализации b1
из {{{"test"}}}
применяется разрешение перегрузки, чтобы выбрать лучший конструктор B
. Поскольку неявное преобразование из {{{"test"}}}
в B&
(инициализатор списка не является lvalue), конструктор B(B&)
не является жизнеспособным. Затем мы фокусируемся на конструкторе B(A)
и проверяем его жизнеспособность.
Чтобы определить неявную последовательность преобразований от {{{"test"}}}
до A
(для простоты я буду использовать обозначение {{{"test"}}}
→ A
), разрешение перегрузки применяется для выбора лучшего конструктора A
, поэтому мы нужно сравнить {{"test"}}
→ const char*
и {{"test"}}
→ std::string
(обратите внимание, что самый внешний слой фигурных скобок устранен) в соответствии с [over. match.list]/1:
Когда объекты неагрегатного типа типа T инициализируются по списку, так что [dcl.init.list] указывает, что разрешение перегрузки выполняется в соответствии с правилами в этом подпункте, разрешение перегрузки выбирает конструктор в две фазы:
Изначально функции-кандидаты являются конструкторами-конструкторами-инициализаторами ([dcl.init.list]) класса T...
Если не найдено жизнеспособного конструктора-списка инициализаторов, снова выполняется разрешение перегрузки, где функции-кандидаты являются всеми конструкторами класса T, а список аргументов состоит из элементов списка инициализаторов.
... В инициализации списка копий, если выбран явный конструктор, инициализация плохо сформирована.
Обратите внимание, что все конструкторы рассматриваются здесь независимо от спецификатора explicit
.
{{"test"}}
→ const char*
не существует в соответствии с [over.ics.list]/10 и [over.ics.list]/11:
В противном случае, если тип параметра не является классом:
если в списке инициализаций есть один элемент , который сам не является списком инициализаторов...
если в списке инициализаторов нет элементов...
Во всех случаях, отличных от перечисленных выше, преобразование невозможно.
Чтобы определить {{"test"}}
→ std::string
, выполняется тот же процесс, и разрешение перегрузки выбирает конструктор std::string
, который принимает параметр типа const char*
.
В результате {{{"test"}}}
→ A
выполняется путем выбора конструктора A(std::string)
.
Варианты
Что делать, если explicit
удален?
Процесс не изменяется. GCC выберет конструктор A(const char*)
, а Кланг выберет конструктор A(std::string)
. Я думаю, что это ошибка для GCC.
Что делать, если в инициализаторе b1
?
есть только два слоя фигурных скобок,
Примечание {{"test"}}
→ const char*
не существует, но существует {"test"}
→ const char*
. Поэтому, если в инициализаторе b1
есть только два слоя фигурных скобок, конструктор A(const char*)
выбирается потому, что {"test"}
→ const char*
лучше, чем {"test"}
→ std::string
. В результате явный конструктор выбирается в инициализации списка копий (инициализация параметра A
в конструкторе B(A)
из {"test"}
), тогда программа плохо сформирована.
Что делать, если объявлен конструктор B(const B&)
?
Обратите внимание, что это также происходит, если объявление B(B&)
удалено. На этот раз нам нужно сравнить {{{"test"}}}
→ A
и {{{"test"}}}
→ const B&
, или {{{"test"}}}
→ const B
эквивалентно.
Чтобы определить {{{"test"}}}
→ const B
, описан процесс, описанный выше. Нам нужно сравнить {{"test"}}
→ A
и {{"test"}}
→ const B&
. Примечание {{"test"}}
→ const B&
не существует в соответствии с [over.best.ics]/4:
Однако, если цель
- первый параметр конструктора или
- параметр неявного объекта определяемой пользователем функции преобразования
и конструктор или пользовательская функция преобразования является кандидатом
- [over.match.ctor], когда аргумент является временным на втором этапе инициализации экземпляра класса,
- [over.match.copy], [над .match.conv] или [над .match.ref] (во всех случаях) или
- вторая фаза [over.match.list], когда список инициализаторов имеет ровно один элемент, который сам является списком инициализаторов, а целью является первый параметр конструктора класса X, а преобразование является X или ссылка на cv X,
пользовательские последовательности преобразований не рассматриваются.
Чтобы определить {{"test"}}
→ A
, описанный выше процесс снова берется. Это почти то же самое, что и в предыдущем подразделе. В результате выбирается конструктор A(const char*)
. Обратите внимание, что здесь выбирается конструктор, чтобы определить {{{"test"}}}
→ const B
и на самом деле не применяется. Это разрешено, хотя конструктор явно.
В результате {{{"test"}}}
→ const B
выполняется путем выбора конструктора B(A)
, затем конструктора A(const char*)
. Теперь оба {{{"test"}}}
→ A
и {{{"test"}}}
→ const B
являются пользовательскими последовательностями преобразования, и ни один не лучше другого, поэтому инициализация b1
неоднозначна.
Что делать, если скобки заменяются скобками?
В соответствии с [over.best.ics]/4, который блокируется в предыдущем подразделе, пользовательское преобразование {{{"test"}}}
→ const B&
не рассматривается. Таким образом, результат будет таким же, как и в первом примере, даже если объявлен конструктор B(const B&)
.