std :: initializer_list в качестве аргумента функции - PullRequest
35 голосов
/ 01 марта 2010

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

Ответы [ 5 ]

55 голосов
/ 01 марта 2010

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

3 голосов
/ 01 марта 2010

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

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

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

2 голосов
/ 01 марта 2010

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

1 голос
/ 13 августа 2011

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

0 голосов
/ 07 декабря 2014

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

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

Удачи

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...