102

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

#include <vector>
#include <memory>

int main() {
    using move_only = std::unique_ptr<int>;
    std::vector<move_only> v { move_only(), move_only(), move_only() };
}

Obviously that cannot work because std::unique_ptr is not copyable:

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete; std::unique_ptr<_Tp, _Dp> = std::unique_ptr]'

Is GCC correct in trying to copy the pointers from the initializer list?

R. Martinho Fernandes
  • 209,766
  • 68
  • 412
  • 492

5 Answers5

70

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());
Xeo
  • 123,374
  • 44
  • 277
  • 381
  • 1
    Ah... this is the rvalue analogue of `std::ref`, non? Maybe it should be called `std::rref`. – Kerrek SB Dec 12 '11 at 01:39
  • IIRC you need an explicitly defined copy constructor in `move_only`, because the spec says that the copy constructor is defined as deleted if the class contains an rvalue reference member. EDIT: Ah NVM, `rref_wrapper` is never copied in your case so it doesn't matter. – Johannes Schaub - litb Dec 12 '11 at 22:13
  • 19
    Now, I guess this should not be left without being mentioned in a comment :) `move_only m[] = { move_only(), move_only(), move_only() }; std::vector v(std::make_move_iterator(m), std::make_move_iterator(m + 3));`. – Johannes Schaub - litb Dec 12 '11 at 22:18
  • 1
    @Johannes: Sometimes, it's the simple solutions that just elude me. Though I gotta admit, I didn't bother with those `move_iterator`s yet. – Xeo Dec 12 '11 at 22:37
  • 3
    @Johannes: Also, why isn't that an answer? :) – Xeo Dec 12 '11 at 22:43
  • Is `v.reserve(init.size())` done when using `std::make_move_iterator`? Not all iterators can provide the number of elements. I came up with a similar solution that just used `vec.reserve(arr.size()); for (auto &v: arr) vec.emplace_back(std::move(v))`. – Johan Lundberg Nov 02 '12 at 22:40
  • 1
    @JohanLundberg: I'd consider that a QoI issue, but I don't see why it couldn't do that. VC++'s stdlib for example tag-dispatches based on the iterator category and uses `std::distance` for forward-or-better iterators and `std::move_iterator` adapts the underlying iterator's category. Anyways, good and concise solution. Post it as an answer, maybe? – Xeo Nov 02 '12 at 22:49
  • I would not have used a rvalue reference member, as you have to ensure that the reference must be consumed before the next sequence point is reached. It seems to me its not the case here, thus yielding undefined behavior. However, you could use a mutable non-reference member to get a similar behavior. Source: http://stackoverflow.com/questions/4176328/undefined-behavior-and-sequence-points – Jean-Bernard Jansen Mar 23 '14 at 17:38
  • @JBJansen Not before the next 'sequence point' (a concept that doesn't exist anymore anyways), but before the end of the full expression, which is the case here. – Xeo Mar 23 '14 at 19:39
  • Can "best solution" from @Johannes be improved by moving most of the remaining horribleness into a helper function? template std::vector VectorOfMoveOnlyFromInitializerList(std::array init) { return vector(std::make_move_iterator(std::begin(init)), std::make_move_iterator(std::end(init))); } Then use it as: std::vector v = VectorOfMoveOnlyFromInitializerList({{move_only(), move_only(), move_only()}}); Wish I didn't need the in caller, but apparently it can't figure out the type without it. – Don Hatch Jun 22 '16 at 09:28
  • See also my answer that uses `make_array()`. Can that be made more direct? – metal Mar 20 '17 at 22:31
50

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
Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • 4
    Consider the in idiom described on cpptruths (http://cpptruths.blogspot.com/2013/09/21-ways-of-passing-parameters-plus-one.html). The idea is to determine lvalue/rvalue at run-time and then call move or copy-construction. in will detect rvalue/lvalue even though the standard interface provided by initializer_list is const reference. – Sumant Sep 24 '13 at 19:34
  • 3
    @Sumant Doesn't seem so "idiomatic to me": isn't it, instead, pure UB? as not just the iterator but rather the underlying elements themselves might be `const`, which cannot be cast away in a well-formed program. – underscore_d Jul 18 '16 at 10:49
11

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)

M.M
  • 130,300
  • 18
  • 171
  • 314
  • 5
    The recursive pack expansion could be replaced by the dummy array comma operator hack , to save a couple of lines of code – M.M Nov 10 '15 at 01:12
3

Update for C++20: Using Johannes Schaub's trick of std::make_move_iterator() with C++20's std::to_array(), you can use a helper function like unto make_tuple() etc., here called make_vector():

#include <array>
#include <memory>
#include <vector>

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 )
{
    return make_vector( std::to_array({ std::forward<T>(t)... }) );
}

int main()
{
    using UX = std::unique_ptr<X>;
    const auto a  = std::to_array({ UX{}, UX{}, UX{} });     // Ok
    const auto v0 = make_vector( UX{}, UX{}, UX{} );         // Ok
    //const auto v2 = std::vector< UX >{ UX{}, UX{}, UX{} }; // !! Error !!
}

See it live on Godbolt.


Similar answer for older C++:

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.

metal
  • 5,609
  • 30
  • 44
-1

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;
    }
}
Gart
  • 2,247
  • 1
  • 26
  • 35
  • That is a terrible idea. Placement new is not a hammer, it is a tool of fine precision. `result.data()` is not a pointer to some random memory. It is a pointer to an **object**. Think of what happens to that poor object when you placement new over it. – R. Martinho Fernandes May 15 '13 at 11:38
  • 1
    Additionally, the array form of placement new is not really usable http://stackoverflow.com/questions/8720425/array-placement-new-requires-unspecified-overhead-in-the-buffer – R. Martinho Fernandes May 15 '13 at 11:42
  • @R. Martinho Fernandes: thanks for pointing out that placement-new for arrays would not work. Now I see why that was a bad idea. – Gart May 15 '13 at 11:55