Ответ 1
У GCC есть ошибка. Стандарт делает это действительным. См:
Обратите внимание, что есть две стороны этого
- Как и какая инициализация выполняется вообще?
- Как инициализация используется во время разрешения перегрузки и какая у нее стоимость?
На первый вопрос отвечает раздел 8.5
. На второй вопрос отвечает раздел 13.3
. Например, привязка привязки обрабатывается в 8.5.3
и 13.3.3.1.4
, тогда как инициализация списка обрабатывается в 8.5.4
и 13.3.3.1.5
.
8.5/14,16
:
Инициализация, которая встречается в форме
T x = a;
, а также при передаче аргументов, возврату функции, исключении (15.1), обработке исключения (15.3) и инициализации элемента агрегации (8.5.1) называется copy-initialization. < ш > .
.
Семантика инициализаторов выглядит следующим образом: [...]: Если инициализатор является скопированным списком инициализации, объект инициализируется списком (8.5.4).
При рассмотрении кандидата function
компилятор увидит список инициализаций (который еще не имеет типа - это просто грамматическая конструкция!) в качестве аргумента, а std::vector<std::string>
- как параметр function
. Чтобы выяснить, что такое стоимость преобразования, и можем ли мы преобразовать их в контексте перегрузки, 13.3.3.1/5
говорит
13.3.3.1.5/1
:
Когда аргумент представляет собой список инициализаторов (8.5.4), это не выражение и специальные правила для его преобразования в тип параметра.
13.3.3.1.5/3
:
В противном случае, если этот параметр является неагрегатным классом X и разрешение перегрузки на 13.3.1.7 выбирает один лучший конструктор X для выполнения инициализации объекта типа X из списка инициализатора аргумента, неявная последовательность преобразования определяемая пользователем последовательность преобразования. Разрешенные пользователем преобразования допускаются для преобразования элементов списка инициализаторов в типы параметров конструктора, за исключением случаев, указанных в 13.3.3.1.
Неагрегатный класс X
равен std::vector<std::string>
, и я рассмотрю один лучший конструктор ниже. Последнее правило дает нам возможность использовать определенные пользователем преобразования в следующих случаях:
struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }
Нам разрешено преобразовать строковый литерал в std::string
, даже если для этого требуется преобразование, определяемое пользователем. Тем не менее, это указывает на ограничения другого абзаца. Что говорит 13.3.3.1
?
13.3.3.1/4
, который является абзацем, ответственным за запрет нескольких пользовательских преобразований. Мы рассмотрим только инициализацию списка:
Однако при рассмотрении аргумента определяемой пользователем функции преобразования [(или конструктора)], которая является кандидатом [...] 13.3.1.7 при передаче списка инициализатора как одного аргумента или когда список инициализаторов имеет ровно один элемент и преобразование в некоторый класс X или ссылку на (возможно, cv-qualified) X рассматривается для первого параметра конструктора X или [...], допускаются только стандартные последовательности преобразования и последовательности преобразования многоточия.
Обратите внимание, что это важное ограничение: если бы это было не так, то вышеописанный может использовать конструктор-копию для создания одинаково хорошо конверсионной последовательности, и инициализация будет неоднозначной. (обратите внимание на потенциальную путаницу "A или B и C" в этом правиле: он должен сказать "(A или B) и C" - поэтому мы ограничены только при попытке преобразования конструктором X, имеющим параметр тип X
).
Мы делегируем 13.3.1.7
для сбора конструкторов, которые мы можем использовать для этого преобразования. Подходим к этому абзацу с общей стороны, начиная с 8.5
, который делегировал нам 8.5.4
:
8.5.4/1
:
Инициализация списка может возникать в контекстах с прямой инициализацией или копированием; инициализация списка в контексте прямой инициализации называется инициализацией прямого списка, а инициализация списка в контексте инициализации копирования называется copy-list-initialization.
8.5.4/2
:
Конструктор - это конструктор списка инициализаторов, если его первый параметр имеет тип
std::initializer_list<E>
или ссылку на возможно cv-quali fiedstd::initializer_list<E>
для некоторого типа E, и либо нет других параметров, либо все остальные параметры имеют значение по умолчанию аргументы (8.3.6).
8.5.4/3
:
Список-инициализация объекта или ссылки типа T определяется следующим образом: [...] В противном случае, если T является типом класса, рассматриваются конструкторы. Если T имеет конструктор списка инициализаторов, список аргументов состоит из списка инициализаторов как одного аргумента; в противном случае список аргументов состоит из элементов списка инициализаторов. Соответствующие конструкторы перечислены (13.3.1.7), а лучший выбирается с помощью разрешения перегрузки (13.3).
В это время T
- тип класса std::vector<std::string>
. У нас есть один аргумент (который еще не имеет типа! Мы просто в контексте наличия списка грамматического инициализатора). Конструкторы перечислены как 13.3.1.7
:
[...] Если T имеет конструктор списка инициализаторов (8.5.4), список аргументов состоит из списка инициализаторов как одного аргумента; в противном случае список аргументов состоит из элементов списка инициализаторов. Для инициализации списка копий все функции-кандидаты являются конструкторами T. Однако, если выбран явный конструктор, инициализация плохо сформирована.
Мы рассмотрим только список инициализаторов std::vector
как единственный кандидат, так как мы уже знаем, что другие не выиграют против него или не будут соответствовать аргументу. Он имеет следующую подпись:
vector(initializer_list<std::string>, const Allocator& = Allocator());
Теперь правила преобразования списка инициализаторов в std::initializer_list<T>
(для категоризации стоимости преобразования аргументов/параметров) перечислены в 13.3.3.1.5
:
Когда аргумент представляет собой список инициализаторов (8.5.4), это не выражение, а специальные правила применяются для преобразования его в тип параметра. [...] Если тип параметра
std::initializer_list<X>
, и все элементы списка инициализаторов могут быть неявно преобразованы в X, неявная последовательность преобразования является наихудшим преобразованием, необходимым для преобразования элемента списка в X. Это преобразование может быть преобразованием, определяемым пользователем, даже в контексте вызова конструктора списка инициализаторов.
Теперь список инициализаторов будет успешно преобразован, а последовательность преобразования будет определяться пользователем (от char const[N]
до std::string
). Как это делается, подробно описано в 8.5.4
:
В противном случае, если T является специализацией
std::initializer_list<E>
, объект initializer_list строится, как описано ниже, и используется для инициализации объекта в соответствии с правилами инициализации объекта из класса того же типа (8.5). (...)
См. 8.5.4/4
, как делается этот последний шаг:)