станд.:: initializer_list как аргумент функции

По некоторым причинам я думал 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"} );
}
35
задан Johannes Schaub - litb 1 October 2011 в 13:54
поделиться

3 ответа

В 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 , как делается этот последний шаг :)

55
ответ дан 27 November 2019 в 07:06
поделиться

Кажется, это работает следующим образом:

function( {std::string("hello"), std::string("world"), std::string("test")} );

Возможно, это ошибка компилятора, но, возможно, вы запрашиваете слишком много неявных преобразований.

3
ответ дан 27 November 2019 в 07:06
поделиться

Навскидку, я не уверен, но подозреваю, что здесь происходит преобразование в initializer_list - это одно преобразование, а преобразование в вектор - это еще одно преобразование. В этом случае вы превышаете лимит только одного неявного преобразования ...

2
ответ дан 27 November 2019 в 07:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: