Can I list-initialize a vector of move-only type?

前端 未结 5 1072
感情败类
感情败类 2020-11-22 08:30

If I pass the following code through my GCC 4.7 snapshot, it tries to copy the unique_ptrs into the vector.

#include 
#include <         


        
相关标签:
5条回答
  • 2020-11-22 08:38

    Edit: Since @Johannes doesn't seem to want to post the best solution as an answer, I'll just do it.

    #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))};
    }
    

    The iterators returned by std::make_move_iterator will move the pointed-to element when being dereferenced.


    Original answer: We're gonna utilize a little helper type here:

    #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;
    

    Sadly, the straight-forward code here won't work:

    std::vector<move_only> v{ rref(move_only()), rref(move_only()), rref(move_only()) };
    

    Since the standard, for whatever reason, doesn't define a converting copy constructor like this:

    // in class initializer_list
    template<class U>
    initializer_list(initializer_list<U> const& other);
    

    The initializer_list<rref_wrapper<move_only>> created by the brace-init-list ({...}) won't convert to the initializer_list<move_only> that the vector<move_only> takes. So we need a two-step initialization here:

    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());
    
    0 讨论(0)
  • 2020-11-22 08:39

    As it has been pointed out, it is not possible to initialize a vector of move-only type with an initializer list. The solution originally proposed by @Johannes works fine, but I have another idea... What if we don't create a temporary array and then move elements from there into the vector, but use placement new to initialize this array already in place of the vector's memory block?

    Here's my function to initialize a vector of unique_ptr's using an argument pack:

    #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;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 08:44

    The synopsis of <initializer_list> in 18.9 makes it reasonably clear that elements of an initializer list are always passed via const-reference. Unfortunately, there does not appear to be any way of using move-semantic in initializer list elements in the current revision of the language.

    Specifically, we have:

    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
    
    0 讨论(0)
  • 2020-11-22 08:48

    Using Johannes Schaub's trick of std::make_move_iterator() with std::experimental::make_array(), you can use a helper function:

    #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 !!
    }
    

    See it live on Coliru.

    Perhaps someone can leverage std::make_array()'s trickery to allow make_vector() to do its thing directly, but I did not see how (more accurately, I tried what I thought should work, failed, and moved on). In any case, the compiler should be able to inline the array to vector transformation, as Clang does with O2 on GodBolt.

    0 讨论(0)
  • 2020-11-22 08:51

    As mentioned in other answers, the behaviour of std::initializer_list is to hold objects by value and not allow moving out, so this is not possible. Here is one possible workaround, using a function call where the initializers are given as variadic arguments:

    #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{});
    }
    

    Unfortunately multi_emplace(foos, {}); fails as it cannot deduce the type for {}, so for objects to be default-constructed you have to repeat the class name. (or use vector::resize)

    0 讨论(0)
提交回复
热议问题