По некоторым причинам я думал C++ 0x позволенный std::initializer_list
как аргумент функции для функций, которые ожидают типы, которые могут быть созданы из такого, например std::vector
. Но по-видимому, это не работает. Это - просто мой компилятор, или это никогда не будет работать? Это из-за потенциальных проблем разрешения перегрузки?
#include <string>
#include <vector>
void function(std::vector<std::string> vec)
{
}
int main()
{
// ok
std::vector<std::string> vec {"hello", "world", "test"};
// error: could not convert '{"hello", "world", "test"}' to 'std::vector...'
function( {"hello", "world", "test"} );
}
В 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) называется копией-инициализацией.
.
.
Семантика инициализаторов следующая [...]: Если инициализатор является списком инициализации в фигурных скобках, объект инициализируется списком (8.5.4).
При рассмотрении функции кандидата
компилятор увидит список инициализаторов (который еще не имеет типа - это просто грамматическая конструкция!) В качестве аргумента и std :: vector < std :: string>
как параметр функции
. Чтобы выяснить, какова стоимость преобразования и можем ли мы преобразовать их в контексте перегрузки, 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
, и я подберу единственный лучший конструктор ниже. Последнее правило позволяет нам использовать определенные пользователем преобразования в следующих случаях:
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
:
Инициализация списка может происходят в контекстах прямой инициализации или копирования-инициализации; Инициализация списка в контексте прямой инициализации называется инициализацией прямого списка , а инициализация списка в контексте инициализации копирования называется инициализацией списка копирования .
8.5.4 / 2
:
Конструктор является конструктором списка инициализаторов , если его первый параметр имеет тип
std :: initializer_list
или ссылка на возможно cv-квалификационныйstd :: initializer_list
для некоторого типа E, и либо нет других параметров, либо все другие параметры имеют аргументы по умолчанию (8.3.6).
8.5.4 / 3
:
Инициализация списка объекта или ссылки типа T определяется следующим образом: [...] В противном случае, если T является типом класса, рассматриваются конструкторы. Если T имеет конструктор списка инициализаторов, список аргументов состоит из списка инициализаторов как одного аргумента; в противном случае список аргументов состоит из элементов списка инициализатора.Применимые конструкторы перечислены (13.3.1.7), и лучший из них выбирается посредством разрешения перегрузки (13.3).
В настоящее время T
является типом класса std :: vector
. У нас есть один аргумент (у которого еще нет типа! Мы просто находимся в контексте наличия списка грамматических инициализаторов). Конструкторы пронумерованы согласно 13.3.1.7
:
[...] Если T имеет инициализатор - конструктор списка (8.5.4), список аргументов состоит из списка инициализаторов в виде единственного аргумента; в противном случае список аргументов состоит из элементов списка инициализатора. Для инициализации списка копирования все функции-кандидаты являются конструкторами T. Однако, если выбран явный конструктор, инициализация будет некорректной.
Мы будем рассматривать только список инициализаторов std :: vector
как единственный кандидат, поскольку мы уже знаем, что другие не победят или не будут соответствовать аргументу. Он имеет следующую сигнатуру:
vector(initializer_list<std::string>, const Allocator& = Allocator());
Теперь правила преобразования списка инициализаторов в std :: initializer_list
(для категоризации стоимости преобразования аргументов / параметров) перечислены в ] 13.3.3.1.5
:
Когда аргумент является списком инициализаторов (8.5.4), это не выражение, и для преобразования его в тип параметра применяются специальные правила. [...] Если тип параметра -
std :: initializer_list
и все элементы списка инициализатора могут быть неявно преобразованы в X, последовательность неявного преобразования является наихудшим преобразованием, необходимым для преобразования элемента списка. в X. Это преобразование может быть определяемым пользователем преобразованием даже в контексте вызова конструктора списка инициализаторов.
Теперь список инициализаторов будет успешно преобразован, и последовательность преобразования является определяемой пользователем преобразованием (из char const [N]
в std :: string
). Как это делается, подробно описано в 8.5.4
еще раз:
В противном случае, если T является специализацией
std :: initializer_list
, объект initializer_list создается, как описано ниже и используется для инициализации объекта в соответствии с правилами инициализации объекта из класса того же типа (8.5). (...)
См. 8.5.4 / 4
, как делается этот последний шаг :)
Кажется, это работает следующим образом:
function( {std::string("hello"), std::string("world"), std::string("test")} );
Возможно, это ошибка компилятора, но, возможно, вы запрашиваете слишком много неявных преобразований.
Навскидку, я не уверен, но подозреваю, что здесь происходит преобразование в initializer_list - это одно преобразование, а преобразование в вектор - это еще одно преобразование. В этом случае вы превышаете лимит только одного неявного преобразования ...