Ответ 1
Насколько я могу судить, это ошибка clang.
Инициализация экземпляра копирования имеет довольно неинтуитивное поведение: он считает явные конструкторы жизнеспособными до тех пор, пока разрешение перегрузки не будет полностью завершено, но затем может отклонить результат перегрузки, если выбран явный конструктор. Формулировка в проекте после N4567, [over.match.list] p1
В инициализации списка копий, если выбран конструктор
explicit
, инициализация плохо сформирована. [Примечание: это отличается от других ситуаций (13.3.1.3, 13.3.1.4), где только преобразования конструкторов рассматриваются для инициализации копирования. Это ограничение применяется только если эта инициализация является частью конечного результата перегрузки разрешающая способность. - конечная нота]
clang HEAD принимает следующую программу:
#include <iostream>
using namespace std;
struct String1 {
explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
String2(const char*) { cout << "String2\n"; }
};
void f1(String1) { cout << "f1(String1)\n"; }
void f2(String2) { cout << "f2(String2)\n"; }
void f(String1) { cout << "f(String1)\n"; }
void f(String2) { cout << "f(String2)\n"; }
int main()
{
//f1( {"asdf"} );
f2( {"asdf"} );
f( {"asdf"} );
}
Который, за исключением комментирования вызова f1
, прямо из Bjarne Stroustrup N2532 - Унифицированная инициализация, глава 4. Спасибо на Йоханнес Шауб, чтобы показать мне эту статью на std-discussion.
В той же главе содержится следующее объяснение:
Реальным преимуществом
explicit
является то, что он отображаетf1("asdf")
ошибка. Проблема в том, что разрешение перегрузки "предпочитает" неexplicit
конструкторы, так чтоf("asdf")
вызываетf(String2)
. Я считаю, что разрешениеf("asdf")
меньше идеала, потому что авторString2
, вероятно, не означал разрешения двусмысленностей в пользуString2
(по крайней мере, не в каждом случае, когда явные и неявные конструкторы происходят так) и писательString1
конечно техника его подводит. Правило благоприятствует "неаккуратным программистам", которые не используютexplicit
.
Насколько мне известно, N2640 - Списки инициализаторов - Альтернативный механизм и Обоснование - последняя статья, которая включает в себя обоснование такого разрешения перегрузки; его преемник N2672 был проголосован в проекте С++ 11.
Из главы "Значение явного":
Первый подход, чтобы сделать пример плохо сформированным, требует, чтобы все конструкторы (явные и неявные) рассматриваются для неявных конверсий, но если выбран явный конструктор, эта программа плохо сформирована. Это правило может ввести свои собственные сюрпризы; например:
struct Matrix { explicit Matrix(int n, int n); }; Matrix transpose(Matrix); struct Pixel { Pixel(int row, int col); }; Pixel transpose(Pixel); Pixel p = transpose({x, y}); // Error.
Второй подход - игнорировать явные конструкторы при поиске для жизнеспособности неявного преобразования, но включить их, когда фактически выбирая конструктор преобразования: если явный выбирается конструктор, программа плохо сформирована. Эта альтернативный подход позволяет использовать последний пример (Pixel-vs-Matrix) как ожидалось (
transpose(Pixel)
), при этом оригинальный пример ( "X x4 = { 10 };
" ) плохо сформирован.
Хотя в этом документе предлагается использовать второй подход, его формулировка представляется ошибочной - в моей интерпретации формулировки она не приводит к поведению, изложенному в обоснованной части статьи. Формулировка пересмотрена в N2672 для использования первого подхода, но я не мог найти никакого обсуждения о том, почему это было изменено.
Есть, конечно, немного больше формулировок, связанных с инициализацией переменной, как в OP, но учитывая разницу в поведении между clang и gcc, то же самое для первой примерной программы в моем ответе, я думаю, что это касается основных моментов.