Инициализатор-список-построение вектора некопируемых (но подвижных) объектов - PullRequest
21 голосов
/ 29 августа 2011

Можно push_back r значений типа некопируемого, но подвижного в вектор этого типа:

#include <vector>

struct S
{
    S(int);
    S(S&&);
};

int main()
{
    std::vector<S> v;
    v.push_back(S(1));
    v.push_back(S(2));
    v.push_back(S(3));
}

Однако, когда я пытаюсь инициализировать список-конструктор-вектор с теми же значениями, я получаю ошибки о необходимости конструктора копирования:

#include <vector>

struct S
{
    S(int);
    S(S&&);
};

int main()
{
    std::vector<S> v = {S(1), S(2), S(3)};
}

Я получаю следующие ошибки с GCC 4.7:

In file included from include/c++/4.7.0/vector:63:0,
                 from test.cpp:1:
include/c++/4.7.0/bits/stl_construct.h: In instantiation of 'void std::_Construct(_T1*, _Args&& ...) [with _T1 = S, _Args = {const S&}]':
include/c++/4.7.0/bits/stl_uninitialized.h:77:3:   required from 'static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const S*, _ForwardIterator = S*, bool _TrivialValueTypes = false]'
include/c++/4.7.0/bits/stl_uninitialized.h:119:41:   required from '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const S*, _ForwardIterator = S*]'
include/c++/4.7.0/bits/stl_uninitialized.h:260:63:   required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = const S*, _ForwardIterator = S*, _Tp = S]'
include/c++/4.7.0/bits/stl_vector.h:1185:4:   required from 'void std::vector<_Tp, _Alloc>::_M_range_initialize(_ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = const S*, _Tp = S, _Alloc = std::allocator<S>]'
include/c++/4.7.0/bits/stl_vector.h:362:2:   required from 'std::vector<_Tp, _Alloc>::vector(std::initializer_list<_Tp>, const allocator_type&) [with _Tp = S, _Alloc = std::allocator<S>, std::vector<_Tp, _Alloc>::allocator_type = std::allocator<S>]'
test.cpp:11:41:   required from here
include/c++/4.7.0/bits/stl_construct.h:77:7: error: no matching function for call to 'S::S(const S&)'
include/c++/4.7.0/bits/stl_construct.h:77:7: note: candidates are:
test.cpp:6:5: note: S::S(S&&)
test.cpp:6:5: note:   no known conversion for argument 1 from 'const S' to 'S&&'
test.cpp:5:5: note: S::S(int)
test.cpp:5:5: note:   no known conversion for argument 1 from 'const S' to 'int'

Должно ли это быть разрешено? Я не вижу никаких технических препятствий для его использования, но в настоящий момент у меня нет под рукой Стандартного ...

Ответы [ 4 ]

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

Может быть, этот пункт из 8.5.4.5 объясняет это (мой акцент):

Объект типа std :: initializer_list создается из список инициализатора, как будто реализация выделяет массив из N элементы типа E, где N - количество элементов в список инициализаторов. Каждый элемент этого массива инициализируется копией с соответствующим элементом списка инициализатора , и Объект std :: initializer_list создан для обращения к этому массиву.

Таким образом, вы можете инициализировать из списков, только если объекты являются копируемыми.


Обновление: как указывает Йоханнес, инициализация копирования может быть реализована как конструкторами копирования, так и переноса, так что одного этого недостаточно для ответа на вопрос. Вот, однако, выдержка из спецификации класса initializer_list, как описано в 18.9:

  template<class _E>
    class initializer_list
    {
    public:
      typedef _E            value_type;
      typedef const _E&     reference;
      typedef const _E&     const_reference;
      typedef size_t        size_type;
      typedef const _E*     iterator;
      typedef const _E*     const_iterator;

Обратите внимание, что нет постоянных typedefs!

Я только что попытался создать конструктор IL, который бы проходил список инициализаторов через std::make_move_iterator, но это не удалось, поскольку const T & нельзя преобразовать в T&&.

Таким образом, ответ таков: вы не можете выйти из IL, потому что так сказано в стандарте.

9 голосов
/ 29 августа 2011

Возможно, это проблема компилятора. Это работает в g ​​++ 4.5.1 ( нажмите для демонстрации онлайн IdeOne)

Заключение : В том смысле, что более ранние реализации g ++ не правильно указывали на ошибку; Списки инициализатора не поддерживают перемещение своих элементов (элементы неявно копируются в процессе). Спасибо Kerrek SB за цитирование полезной фразы из стандарта.


Старое производство (для понимания комментариев:)

Редактировать Обнаружено, что, по крайней мере, g ++ 4.6.1+, похоже, имеет вашу жалобу на этот код.

Редактировать После прочтения источника в std::initializer_list<T> У меня начинает складываться впечатление, что это не поддерживается библиотекой (это выглядит намеренно). Допускает ли стандарт, чтобы список инициализаторов forward xvalue-ness его элементов ... Я не удивлюсь, если они остановятся на этом (совершенная пересылка все еще нелегка я думаю, что поддерживается в C ++ 0x, и не все параметры инициализатора должны иметь одинаковый (вычитаемый) тип.

Кто-нибудь с более стандартными под его поясом заботится, чтобы помочь? http://www.open -std.org / ОТК1 / SC22 / wg21 / документы / документы / 2008 / n2640.pdf

#include <vector>

struct S
{
    S(int) {};
    S(S&&) {};
};

int main()
{
    std::vector<S> v = {S(1), S(2), S(3)};
    std::vector<S> w = {std::move(S(1)), std::move(S(2)), std::move(S(3))};

    std::vector<S> or_even_just = {1, 2, 3};
}

8 голосов
/ 29 августа 2011

Initializer_list предоставляет только константные ссылки и константные итераторы.Вектор не может двигаться от этого.

template<class E> 
class initializer_list {
public:
    typedef E value_type;

    typedef const E& reference;
    typedef const E& const_reference;

    typedef size_t size_type;

    typedef const E* iterator;
    typedef const E* const_iterator;
4 голосов
/ 26 марта 2017

Кажется, что ответ - нет за Ответ Kerrek SB .Но вы можете добиться чего-то подобного с помощью небольших вспомогательных функций, используя шаблоны с переменным числом аргументов:

#include <vector>
#include <utility>

template <typename T>
void add_to_vector(std::vector<T>* vec) {}

template <typename T, typename... Args>
void add_to_vector(std::vector<T>* vec, T&& car, Args&&... cdr) {
  vec->push_back(std::forward<T>(car));
  add_to_vector(vec, std::forward<Args>(cdr)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
  std::vector<T> result;
  add_to_vector(&result, std::forward<Args>(args)...);
  return result;
}

struct S {
  S(int) {}
  S(S&&) {}
};

int main() {
  std::vector<S> v = make_vector<S>(S(1), S(2), S(3));
  return 0;
}
...