How to List-Initialize a Vector of Move-Only Type

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

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

Inserting into a vector of move-only type

"Efficient" is something to be measured. But if your desire is to shift the elements in one go instead of constantly moving them one item to the right, you can do it with std::rotate. Here's how

vec.resize(vec.size() + size); // Add the "null" pointers to the end.
// Obtain valid first after resize
std::rotate(first, vec.end() - size, vec.end());

Since the function of rotate is to make the middle iterator the "new first" of the range, while the iterator preceding it is the "new last", the above choice of iterators will shift the range of null pointers to their intended location (before first).

Furthermore, since you tagged C++17, you can also pass the standard algorithm an execution policy, and hopefully gain some parallelism to boot.

std::list of move-only type: Cannot emplace into std::vector in VC++

MSVC uses the copy constructor of std::list because its move constructor is throwing. During reallocation, if the move constructor throws, std::vector cannot provide strong exception guarantee as required by the standard.

In your case, the vector does not have any element before reallocation, so it appears that the copy constructor is not called, but that doesn't mean the copy constructor is not needed.

std::list in libstdc++ and libc++ has noexcept move constructor. This is permitted but not required by the standard.

std::vector initialization move/copy constructor of the element

vector<Foo> v{std::move(foo)};

Here you're calling the vector constructor that takes an std::initializer_list. An initializer list only allows const access to its elements, so the vector is going to have to copy each element from the initializer_list to its own storage. That's what causes the call to the copy constructor.

From §8.5.4/5 [dcl.init.list]

An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list.

See also https://tristanbrindle.com/posts/beware-copies-initializer-list


As for the extra move constructor call with -fno-elide-constructors, this was discussed in another answer a couple of days ago. It seems as though g++ takes a very literal approach to the example implementation of an initializer_list shown in the standard in same section I've quoted above.

The same example, when compiled using clang, doesn't produce the extra move constructor call.

Initializer-list-constructing a vector of noncopyable (but movable) objects

Maybe this clause from 8.5.4.5 explains it (my emphasis):

An object of type std::initializer_list is constructed from an
initializer list as if the implementation allocated an array of N
elements of type E, where N is the number of elements in the
initializer list. Each element of that array is copy-initialized
with the corresponding element of the initializer list
, and the
std::initializer_list object is constructed to refer to that array.

So you can only initialize from lists if the objects are copyable.


Update: As Johannes points out, copy-initialization can be realized by both copy and move constructors, so that alone isn't enough to answer the question. Here is, however, an excerpt of the specification of the initializer_list class as described in 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;

Note how there are no non-constant typedefs!

I just tried making an IL constructor which would traverse the initializer list via std::make_move_iterator, which failed because const T & cannot be converted to T&&.

So the answer is: You cannot move from the IL, because the standard says so.

initializer_list and move semantics

No, that won't work as intended; you will still get copies. I'm pretty surprised by this, as I'd thought that initializer_list existed to keep an array of temporaries until they were move'd.

begin and end for initializer_list return const T *, so the result of move in your code is T const && — an immutable rvalue reference. Such an expression can't meaningfully be moved from. It will bind to an function parameter of type T const & because rvalues do bind to const lvalue references, and you will still see copy semantics.

Probably the reason for this is so the compiler can elect to make the initializer_list a statically-initialized constant, but it seems it would be cleaner to make its type initializer_list or const initializer_list at the compiler's discretion, so the user doesn't know whether to expect a const or mutable result from begin and end. But that's just my gut feeling, probably there's a good reason I'm wrong.

Update: I've written an ISO proposal for initializer_list support of move-only types. It's only a first draft, and it's not implemented anywhere yet, but you can see it for more analysis of the problem.

i am unable to initilize vector with initilize elements

Thanks @BoP.
I have just changed my settings to c++14 and it worked image.

Why move constructor and assignement not being called for initializing a vector with initialize_list

I expect the vectors to be filled in place

Both push_back and the initializer list constructor expect that the element is already constructed and passed through the parameter. Therefore the elements must first be constructed via conversion and then moved/copied into the vector's storage. If you don't want that, then use emplace_back instead, which takes constructor arguments for the element's constructor and creates the element in-place.

This doesn't resolve all cases of copy and moves, since the vector must move objects to new storage if the old one becomes too small to hold more elements. To avoid this completely, first call vec.reserve(...) where ... is at least as large as the maximum size that the vector will have.

The reason copy instead of move is used in case of reallocation is because you didn't mark your move constructor noexcept. std::vector prefers the copy constructor if the move constructor is not noexcept, because if a move constructor throws while moving the objects to new storage, then std::vector cannot guarantee that it will be able to roll-back to the previous state, as it would usually do.


Declaring copy/move constructor/assignment or a destructor in a class is always a bit risky. Firstly, because how easy it is to cause undefined behavior, see rule of three/five and secondly because it often results in worse results than the implicitly generated versions if not carefully written.

If you don't have a specific reason, e.g. because you manage a raw resource in the class, then don't declare any of these special members. (Only) Then the implicit ones will do the correct and most-likely best possible thing automatically ("rule of zero").

vectorunique_ptrBase using initialization list of Derived

Adding extra constructor

template <typename U>
movable_il(U&& in): t(std::forward<U>(in)) {}

fixes compilation.

Demo.

Initialize a std::vector of structures to zero

Yes it is guaranteed since A{} is value initialization and from cppreference:

Zero initialization is performed in the following situations:

  • As part of value-initialization sequence for non-class types and for members of value-initialized class types that have no constructors, including value initialization of elements of aggregates for which no initializers are provided.

You can also use:

std::vector<A> my_vector(100, {0,0,0}); 


Related Topics



Leave a reply



Submit