Могу ли я перечислить-инициализировать вектор типа только для перемещения?
Если я передаю следующий код через мой снимок GCC 4.7, он пытается скопировать unique_ptr
s на вектор.
#include <vector>
#include <memory>
int main() {
using move_only = std::unique_ptr<int>;
std::vector<move_only> v { move_only(), move_only(), move_only() };
}
очевидно, что это не может работать, потому что std::unique_ptr
не копировать:
ошибка: использование удаленной функции 'с std::unique_ptr не::unique_ptr не(константные СТД::unique_ptr является&) [с _Tp = инт; _Dp = СТД::default_delete; и std::unique_ptr не = с std::unique_ptr не]'
правильно ли GCC пытается скопировать указатели из списка инициализаторов?
5 ответов:
резюме
<initializer_list>
в 18.9 достаточно ясно показано, что элементы списка инициализаторов всегда передаются через const-reference. К сожалению, в текущей редакции языка не существует способа использования move-semantic в элементах списка инициализаторов.в частности, у нас есть:
typedef const E& reference; typedef const E& const_reference; typedef const E* iterator; typedef const E* const_iterator; const E* begin() const noexcept; // first element const E* end() const noexcept; // one past the last element
Edit: поскольку @Johannes, похоже, не хочет публиковать лучшее решение в качестве ответа, я просто сделаю это.
#include <iterator> #include <vector> #include <memory> int main(){ using move_only = std::unique_ptr<int>; move_only init[] = { move_only(), move_only(), move_only() }; std::vector<move_only> v{std::make_move_iterator(std::begin(init)), std::make_move_iterator(std::end(init))}; }
Iterators, возвращенные
std::make_move_iterator
при разыменовании будет перемещен элемент с указателем.
оригинальный ответ: Мы собираемся использовать небольшой тип помощника здесь:
#include <utility> #include <type_traits> template<class T> struct rref_wrapper { // CAUTION - very volatile, use with care explicit rref_wrapper(T&& v) : _val(std::move(v)) {} explicit operator T() const{ return T{ std::move(_val) }; } private: T&& _val; }; // only usable on temporaries template<class T> typename std::enable_if< !std::is_lvalue_reference<T>::value, rref_wrapper<T> >::type rref(T&& v){ return rref_wrapper<T>(std::move(v)); } // lvalue reference can go away template<class T> void rref(T&) = delete;
к сожалению, прямой код здесь не будет работать:
std::vector<move_only> v{ rref(move_only()), rref(move_only()), rref(move_only()) };
начиная со стандарта, для независимо от причины, не определяет конструктор преобразования копий следующим образом:
// in class initializer_list template<class U> initializer_list(initializer_list<U> const& other);
The
initializer_list<rref_wrapper<move_only>>
созданный brace-init-list ({...}
) не преобразуется вinitializer_list<move_only>
чтоvector<move_only>
берет. Поэтому нам нужна двухэтапная инициализация здесь:std::initializer_list<rref_wrapper<move_only>> il{ rref(move_only()), rref(move_only()), rref(move_only()) }; std::vector<move_only> v(il.begin(), il.end());
как уже упоминалось в других ответов, поведение
std::initializer_list
- это удерживать объекты по значению и не допускать их перемещения, поэтому это невозможно. Вот один из возможных обходных путей, используя вызов функции, где инициализаторы задаются как вариативные аргументы:#include <vector> #include <memory> struct Foo { std::unique_ptr<int> u; int x; Foo(int x = 0): x(x) {} }; template<typename V> // recursion-ender void multi_emplace(std::vector<V> &vec) {} template<typename V, typename T1, typename... Types> void multi_emplace(std::vector<V> &vec, T1&& t1, Types&&... args) { vec.emplace_back( std::move(t1) ); multi_emplace(vec, args...); } int main() { std::vector<Foo> foos; multi_emplace(foos, 1, 2, 3, 4, 5); multi_emplace(foos, Foo{}, Foo{}); }
к сожалению
multi_emplace(foos, {});
не удается, так как он не может вывести тип для{}
, поэтому для объектов, которые будут построены по умолчанию, вы должны повторить имя класса. (или использоватьvector::resize
)
используя трюк Йоханнеса Шауба
std::make_move_iterator()
Сstd::experimental::make_array()
, вы можете использовать вспомогательную функцию:#include <memory> #include <type_traits> #include <vector> #include <experimental/array> struct X {}; template<class T, std::size_t N> auto make_vector( std::array<T,N>&& a ) -> std::vector<T> { return { std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)) }; } template<class... T> auto make_vector( T&& ... t ) -> std::vector<typename std::common_type<T...>::type> { return make_vector( std::experimental::make_array( std::forward<T>(t)... ) ); } int main() { using UX = std::unique_ptr<X>; const auto a = std::experimental::make_array( UX{}, UX{}, UX{} ); // Ok const auto v0 = make_vector( UX{}, UX{}, UX{} ); // Ok //const auto v1 = std::vector< UX >{ UX{}, UX{}, UX{} }; // !! Error !! }
смотрите его в прямом эфире Coliru.
возможно, кто-то может использовать
std::make_array()
обман, чтобы позволитьmake_vector()
чтобы сделать свое дело напрямую, но я не видел, как (точнее, я попробовал то, что, по моему мнению, должно работать, потерпел неудачу и двинулся дальше). В любом случае компилятор должен иметь возможность встроить преобразование массива в вектор, как это делает Clang О2 на GodBolt.
как уже было указано, инициализировать вектор типа "только перемещение" со списком инициализаторов невозможно. Решение, первоначально предложенное @Johannes, отлично работает, но у меня есть другая идея... Что, если мы не создаем временный массив, а затем переместить элементы в вектор, но использовать помещение
new
инициализировать этот массив уже вместо блока памяти вектора?вот моя функция для инициализации вектора
unique_ptr
использует аргумент пакет:#include <iostream> #include <vector> #include <make_unique.h> /// @see http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding template <typename T, typename... Items> inline std::vector<std::unique_ptr<T>> make_vector_of_unique(Items&&... items) { typedef std::unique_ptr<T> value_type; // Allocate memory for all items std::vector<value_type> result(sizeof...(Items)); // Initialize the array in place of allocated memory new (result.data()) value_type[sizeof...(Items)] { make_unique<typename std::remove_reference<Items>::type>(std::forward<Items>(items))... }; return result; } int main(int, char**) { auto testVector = make_vector_of_unique<int>(1,2,3); for (auto const &item : testVector) { std::cout << *item << std::endl; } }