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-квалифицированное) X рассматривается для первого параметра конструктора X или [...], допускаются только стандартные последовательности преобразования и последовательности преобразования эллипса.
Обратите внимание, что это важное ограничение: если бы не было этого, вышеприведенное может использовать конструктор копирования для установления последовательности преобразования одинаково хорошо, и инициализация будет неоднозначной. (обратите внимание на возможную путаницу «A или B и C» в этом правиле: подразумевается «(A или B) и C»), поэтому мы ограничены только при попытке преобразования с помощью конструктора X имеет параметр типа X
).
Мы делегированы 13.3.1.7
для сбора конструкторов, которые мы можем использовать для этого преобразования. Давайте подойдем к этому абзацу с общей стороны, начиная с 8.5
, который делегировал нам 8.5.4
:
8.5.4/1
:
List-инициализация может происходить в контексте прямой инициализации или копирования-инициализации; инициализация списка в контексте прямой инициализации называется direct-list-initialization , а инициализация списка в контексте инициализации копирования называется copy-list-initialization .
8.5.4/2
Конструктор - это конструктор списка инициализатора , если его первый параметр имеет тип std::initializer_list<E>
или ссылку на возможно cv-квалифицированное std::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
, как делается этот последний шаг:)