Initializer-List-Constructing a Vector of Noncopyable (But Movable) Objects

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.

How to create a map from initializer_list with non-copyable members in C++?

So, I worked a bit on the constructor and managed to come up with the solution myself. As my objects are not copyable, there were no downsides to making a second argument of the initializer_list's pair an rvalue.

std::initializer_list<std::pair<const std::string, ExitMessage&&>>

However, now I couldn't use a default constructor for the map, so I had to write an initialization loop myself.

ExitMessage::ExitMessage( const std::string& text,
std::initializer_list<std::pair<const std::string, ExitMessage&&>>&& subMessages )
: _text( text ) {
_subMessages = std::make_unique<std::map<std::string, ExitMessage>>();

for (const std::pair<const std::string, ExitMessage&&>& subMessage : subMessages) {
auto& subMessageRef = const_cast<std::pair<const std::string, ExitMessage&&>&>(subMessage);
_subMessages->emplace( subMessageRef.first, std::move( subMessageRef.second ) );
}
}

As I'm now sure that parameter objects are either temporaries or explicitly moved (that was still a requirement before, due to a deleted copy constructor, just not for the list itself), I can safely unconst initializer_list's members and manually move them into the map.

Why isn't move construction used when initiating a vector from initializer list (via implicit constructor)

I thought I had an initializer list of integers, but I'm now wondering if what I have in between is an initializer list of Cs, which can't be moved from (as its const). Is this a correct interpretation?

This is correct. vector<C> does not have an initializer_list<int> constructor or even an initializer_list<T> constructor for some template parameter T. What it does have is an initializer_list<C> constructor - which is built up from all the ints you pass in. Since the backing of initializer_list is a const array, you get a bunch of copies instead of a bunch of moves.

Non-copyable elements in vector

Yes you can have std::vector<NotCopyable> if NotCopyable is movable:

struct NotCopyable
{
NotCopyable() = default;
NotCopyable(const NotCopyable&) = delete;
NotCopyable& operator = (const NotCopyable&) = delete;

NotCopyable(NotCopyable&&) = default;
NotCopyable& operator = (NotCopyable&&) = default;
};

int main()
{
std::vector<NotCopyable> v;
NotCopyable nc;

v.push_back(NotCopyable{});
v.emplace_back();
v.push_back(std::move(nc));
}

Live example.

In place construction of a pair of nonmovable, non copyable in a std::vector

std::vector is subject to reallocation once the size reach the capacity.
when reallocating the elements into a new memory segment std::vector has to copy/move the values from the old segment and this is made by calling copy/move constructors.

if you don't need that the elements are sequential in memory you can use std::deque instead, since std::deque doesn't reallocate the elements internally.

you can't store non copyable and non moveable objects into std::vectors.


EDIT suggested by @François Andrieux

In case you still need for any reason an std::vector you may think to use a vector made using std::unique_ptr<X> as value type using std::vector<std::unique_ptr<X>>.

With this solution you still don't get a sequential order in memory of your elements, and they are keep in memory till they are still in the vector, so except in case you are forced by any reason to use std::vectors, i think the best match is still the std::deque.



Related Topics



Leave a reply



Submit