Почему emplace_back() не использует равномерную инициализацию?

Следующий код:

#include <vector>

struct S
{
    int x, y;
};

int main()
{
    std::vector<S> v;
    v.emplace_back(0, 0);
}

Выдает следующие ошибки при компиляции с помощью GCC:

In file included from c++/4.7.0/i686-pc-linux-gnu/bits/c++allocator.h:34:0,
                 from c++/4.7.0/bits/allocator.h:48,
                 from c++/4.7.0/vector:62,
                 from test.cpp:1:
c++/4.7.0/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = S; _Args = {int, int}; _Tp = S]':
c++/4.7.0/bits/alloc_traits.h:265:4:   required from 'static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]'
c++/4.7.0/bits/alloc_traits.h:402:4:   required from 'static void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>]'
c++/4.7.0/bits/vector.tcc:97:6:   required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, int}; _Tp = S; _Alloc = std::allocator<S>]'
test.cpp:11:24:   required from here
c++/4.7.0/ext/new_allocator.h:110:4: error: new initializer expression list treated as compound expression [-fpermissive]
c++/4.7.0/ext/new_allocator.h:110:4: error: no matching function for call to 'S::S(int)'
c++/4.7.0/ext/new_allocator.h:110:4: note: candidates are:
test.cpp:3:8: note: S::S()
test.cpp:3:8: note:   candidate expects 0 arguments, 1 provided
test.cpp:3:8: note: constexpr S::S(const S&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'const S&'
test.cpp:3:8: note: constexpr S::S(S&&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'S&&'

Предполагая, что vector использует обычный конструктор конструктора () для построения элемента из аргументов в emplace_back(). Почему вместо vector использовать синтаксис равносильной инициализации {}, чтобы сделать примеры, подобные описанным выше?

Мне кажется, что нечего терять, используя {} (он вызывает конструктор, когда он есть, но все же работает, когда его нет), и это будет больше в духе C + +11 для использования {} - в конце концов, вся точка единообразной инициализации заключается в том, что она используется равномерно - то есть везде - для инициализации объектов.

Ответ 1

Великие мысли тоже думают; v). Я представил отчет о дефектах и ​​предложил изменить стандарт на эту тему.

http://cplusplus.github.com/LWG/lwg-active.html#2089

Кроме того, Люк Дантон помог мне понять сложность: Прямая или стандартная инициализация в std:: allocator.

Когда EmplaceConstructible (23.2.1 [container.requirements.general]/13) для инициализации используется возникает объект с прямой инициализацией. Инициализация агрегата или использование конструктора std:: initializer_list с emplace требует именования инициализированный тип и перемещение временного. Это результат std:: allocator:: construct using direct-initialization, not list-initialization (иногда называемая "равномерной инициализацией" ) синтаксис.

Изменение std:: allocator:: construct для использования инициализации списка будет, среди прочего, отдавать предпочтение std:: initializer_list перегрузки конструктора, нарушение действующего кода в неинтуитивном и неприменимый способ - для emplace_back не будет доступа для доступа к конструктор, вытесненный std:: initializer_list, без существенного повторное выполнение push_back.

std::vector<std::vector<int>> v;
v.emplace_back(3, 4); // v[0] == {4, 4, 4}, not {3, 4} as in list-initialization

Предлагаемый компромисс заключается в использовании SFINAE с std:: is_constructible, который проверяет правильность формирования прямой инициализации. Если is_constructible является ложным, тогда альтернатива std:: allocator:: конструкция перегрузки выбрана, которая использует Список инициализация. Поскольку инициализация списка всегда возвращается к прямая инициализация, пользователь увидит диагностические сообщения, как будто инициализация списка (унифицированная инициализация) всегда использовалась, потому что перегрузка с прямой инициализацией не может потерпеть неудачу.

Я вижу два угловых случая, которые открывают пробелы в этой схеме. Один происходит когда аргументы, предназначенные для std:: initializer_list, удовлетворяют конструктор, такой как попытка вставления - вставить значение {3, 4} в выше пример. Обходной путь состоит в том, чтобы явно указать std:: initializer_list, как в v.emplace_back (std:: initializer_list (3, 4)). Поскольку это соответствует семантика, как если бы был выведен std:: initializer_list, кажется, что здесь не будет настоящей проблемы.

В другом случае аргументы, предназначенные для агрегатной инициализации удовлетворяют конструктору. Поскольку агрегаты не могут быть определены пользователем конструкторов, это требует, чтобы первый нестатический член данных совокупность неявно конвертируется из совокупного типа и что список инициализаторов имеет один элемент. Обходной путь поставьте инициализатор для второго элемента. По-прежнему невозможно на месте построить совокупность с одним нестатическим элементом данных посредством преобразование из типа, конвертируемого в собственный тип агрегата. Эта кажется приемлемой маленькой дырой.