Подтвердить что ты не робот

Std:: initializer_list как аргумент функции

По какой-то причине я думал, что С++ 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"} );
}
4b9b3361

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

Ответ 2

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

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

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

Ответ 3

Оффлайн, я не уверен, но я подозреваю, что здесь происходит то, что преобразование в initializer_list - это одно преобразование, а преобразование в вектор - другое преобразование. Если это случай, вы превысите предел только одного неявного преобразования...

Ответ 4

Это либо ошибка компилятора, либо ваш компилятор не поддерживает std:: initializer_list. Протестировано на GCC 4.5.1, и оно компилируется отлично.

Ответ 5

Вам нужно указать тип вашего initializer_list

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

Удачи.