Почему 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 в духе C++11 {} В конце концов, весь смысл равномерной инициализации состоит в том, что она используется единообразно, то есть везде, для инициализации объектов.

1 ответ

Решение

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

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

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

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

Изменение 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:: construct, которая использует инициализацию списка. Так как инициализация списка всегда выполняется при прямой инициализации, пользователь увидит диагностические сообщения, как если бы всегда использовалась инициализация списка (равномерная инициализация), поскольку перегрузка прямой инициализации не может завершиться сбоем.

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

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

Другие вопросы по тегам