Почему emplace_back () не использует равномерную инициализацию? - PullRequest
60 голосов
/ 09 января 2012

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

#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 Ответ

50 голосов
/ 09 января 2012

Великие умы думают одинаково; 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 с использованием прямой инициализации, а не синтаксиса инициализации списка (иногда называемой «равномерной инициализацией»).

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

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

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

...