Инициализатор-список-построение вектора не копируемых (но подвижных) объектов
Можно push_back
Значения некопируемого, но подвижного типа в вектор этого типа:
#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));
}
Однако, когда я пытаюсь инициализировать-list-list-конструировать вектор с теми же значениями, я получаю ошибки о необходимости конструктора копирования:
#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 ответа
Может быть, этот пункт из 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, потому что стандарт так говорит.
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;
Похоже, это может быть проблема компилятора. Это работает в g ++ 4.5.1 (нажмите для демонстрации онлайн IdeOne)
Вывод: в том смысле, что более старые реализации g++ не правильно указывали на ошибку; Списки инициализатора не поддерживают перемещение своих элементов (элементы неявно копируются в процессе). Спасибо Kerrek SB за цитирование полезной фразы из стандарта.
Старое производство (для понимания комментариев:)
Изменить Обнаружено, что по крайней мере g++ 4.6.1+, кажется, есть ваша жалоба на этот код.
Изменить После прочтения источника std::initializer_list<T>
У меня начинает складываться впечатление, что это не поддерживается библиотекой (это выглядит намеренно). Разрешает ли стандарт на самом деле список инициализатора для пересылки xvalue-ness его элементов... Я не удивлюсь, если они остановились (идеальная пересылка все еще не так легко поддерживается в C++0x, я думаю, и не все Параметры инициализатора должны иметь такой же (вычитаемый) тип.
Кто-нибудь с более стандартным под его поясом заботиться, чтобы помочь? http://www.open-std.org/jtc1/sc22/wg21/docs/papers/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};
}
Кажется, ответ - нет, согласно ответу Керрека С.Б. Но вы можете добиться чего-то подобного с помощью небольших вспомогательных функций, используя шаблоны с переменным числом аргументов:
#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;
}