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.
how to move elements of an initializer_list?
There is no way to avoid the copying from an initializer_list<string>
, because the standard defines the invocation of a constructor taking an initializer list argument, from a curly braces initializer as actual argument, as follows (emphasis added):
” An object of type
std::initializer_list<E>
is constructed from an initializer list as if the implementation allocated a temporary array ofN
elements of typeconst E
, whereN
is the number of elements in the
initializer list
IMHO this is really unfortunate.
A workaround (for your own classes) is to accept initializer_list<char const*>
.
Here's an example of the workaround applied to std::vector<string>
. For that, where you don't control the class' code, it involves declaring a data array (actually an initializer_list
) explicitly. This is just as with C++03, which the initializer list mechanism was intended to avoid:
#include <vector>
#include <initializer_list>
#include <iostream>
#include <iterator> // std::begin, std::end
using namespace std;
struct My_string
{
char const* const ps;
My_string( char const* const s )
: ps( s )
{
cout << " My_string(*) <- '" << s << "'" << endl;
}
My_string( My_string const& other )
: ps( other.ps )
{
cout << " My_string(const&) <- '" << other.ps << "'" << endl;
};
My_string( My_string&& other )
: ps( other.ps )
{
cout << " My_string(&&) <- '" << other.ps << "'" << endl;
};
};
auto main() -> int
{
cout << "Making vector a." << endl;
vector<My_string> const a = {"a1", "a2", "a3"};
cout << "Making data for vector b." << endl;
auto const b_data = { "b1", "b2", "b3" };
cout << "Making vector b." << endl;
vector<My_string> const b( begin( b_data ), end( b_data ) );
}
Output:
Making vector a.
My_string(*) <- 'a1'
My_string(*) <- 'a2'
My_string(*) <- 'a3'
My_string(const&) <- 'a1'
My_string(const&) <- 'a2'
My_string(const&) <- 'a3'
Making data for vector b.
Making vector b.
My_string(*) <- 'b1'
My_string(*) <- 'b2'
My_string(*) <- 'b3'
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.
Is it safe to move elements of a initializer list?
initializer_list
only provides const
access to its elements. You could use const_cast
to make that code compile, but then the moves might end up with undefined behaviour (if the elements of the initializer_list
are truly const). So, no it is not safe to do this moving. There are workarounds for this, if you truly need it.
using an initializer list where the values contain unique_ptr member variables
It's fault of std::initializer_list
, if you look at it's begin/end member functions, they return const T*
, which means it will force std::map
to try use the copy constructor of your Foo
, which is deleted
as std::unique_ptr
can not be copied.
This issue is not unique to std::map
, any container which allows you to initialize it with std::initializer_list
will really copy the arguments from an initializer list.
The C++ standard requires that during such initialization there's a temporary const T[N]
array, which the std::initializer_list
points to, const
disables moves from it.
Why are copy and move constructors called together?
std::vector
can be constructed from a std::initializer_list
, and you are calling that constructor. The rules for initializer_list construction state that this constructor is aggressively preferred:
A constructor is an initializer-list constructor if its first parameter is of type
std::initializer_list<E>
or reference to possibly cv-qualifiedstd::initializer_list<E>
for some typeE
, and either there are
no other parameters or else all other parameters have default arguments (8.3.6). [ Note: Initializer-list
constructors are favored over other constructors in list-initialization <...>]
Also, because of the sort of weird implementation of an initializer_list
as an array allocated under the hood, elements of the corresponding array that the std::initializer_list<E>
refers to are forced to be copy initialized (which can be elided):
An object of type
std::initializer_list<E>
is constructed from an initializer list as if the implementation
allocated an array ofN
elements of typeE
, whereN
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
thestd::initializer_list<E>
object is constructed to refer to that array
(Both references above from N3337 [dcl.init.list])
However, in your first example the copies can/are elided despite the name ([dcl.init]/14) so you don't see an extra copy construction (they can also be moved) You can thank your compiler for that, because copy elision is not required in C++11 (although it is in C++17).
See [class.copy] for more details ("When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class
object...").
The final part is key:
[support.initlist] states that
An object of type
initializer_list<E>
provides access to an array of objects of typeconst E
.
This means that the std::vector
cannot take over the memory directly; it must be copied, this is where you ultimately see the copy constructions being called.
In the second example it is as Kerrek SB stated, you prevented the copy-elision I mentioned earlier and caused an additional overhead of a move.
Related Topics
Can Someone Explain This Template Code That Gives Me the Size of an Array
C++: What Is the Size of an Object of an Empty Class
C++ Cross-Platform High-Resolution Timer
What Is Iaca and How to Use It
Is a String Literal in С++ Created in Static Memory
How to Pass a Multidimensional Array to a Function in C and C++
Can a C++ Class Include Itself as an Member
Accessing Protected Members in a Derived Class
Can Modern X86 Hardware Not Store a Single Byte to Memory
Is (4 ≫ Y ≫ 1) a Valid Statement in C++? How to Evaluate It If So
Rand() Returns Same Values When Called Within a Single Function
Remove Spaces from Std::String in C++
Developing C Wrapper API For Object-Oriented C++ Code
C++ Lambda With Captures as a Function Pointer
C++ Static Initialization Order