18

Let's say you have a variable of type std::vector<std::string> and you initialize it with an initializer list:

using V = std::vector<std::string>; V v = { "Hello", "little", "world", "of", "move", "semantics" }; 

The compiler will create a temporary std::string for each string literal, create an initializer list over these and then call the ctor for V and create the vector. The ctor does not know that all those strings are temporaries, so it is copying each string.

I haven't found anything in the standard which allows the vector ctor to move the elements when they are temporaries.

Am I missing something or does using initializer lists lead to unnecessary copies? I am writing classes where this problem could lead to significantly inefficient code. Any technique to avoid unnecessary copies would be greatly appreciated.

3
  • Good question. I don't think initializer lists were originally intended to do anything but pass a sequence of temporaries around. But now you can declare an initializer list variable. It wouldn't do for a vector to suck the innards out of each item in such a long lived list. But wait, constructor with rvalue ref to initializer list. Maybe? Commented Apr 2, 2016 at 19:30
  • @Cheersandhth.-Alf I guess I can overload on an initializer list by const-ref, non-const-ref and rvalue-ref. I can then move the elements from the rvalue-ref initializer list manually. Not elegant and doesn't work with the STL containers I'm currently using as underlying storage (vector and map) but better than nothing. Thanks. Commented Apr 2, 2016 at 19:45
  • 1
    duplicate of initializer_list and move semantics Commented Jul 5, 2017 at 9:16

3 Answers 3

7

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):

C++14 §8.5.4/5

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

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' 
Sign up to request clarification or add additional context in comments.

5 Comments

You can also use a variadic template constructor.
const E? Thank is really unfortunate. Is there any reason for this? Otherwise, detecting an rvalue initializer list would at least allow me to move manually, but with const E, that seems illegal!? (And I am not really using std::string, I'm using a type I wrote that can be more expensive to copy)
I think I found a solution by using mutable, I added a self-answer. What do you think about it?
Neat. I didn't think of that. I can't see that it helps much with vector<string>, but should great for user-defined classes. I think the proxy thing can be generalized to a general mutable_ class template. I.e. writing mutable_<my_string> instead of my_string_proxy.
In case you are interested: Here's the real-world use-case I have... github.com/taocpp/json
4

After some thinking, I came up with a solution based on mutable. The other answer is still mostly correct, but one can create a proxy with a mutable member to get rid of the top-level const-ness and then move the elements from there. Methods taking an initializer list should therefore overload for a const-ref initializer list and an rvalue-ref version to know when they are allowed to move.

Here's a working example, it might look arbitrary at first but in my real-world use-case, it solved the problem.

#include <iostream> #include <vector> // to show which operations are called struct my_string { const char* s_; my_string( const char* s ) : s_( s ) { std::cout << "my_string(const char*) " << s_ << std::endl; } my_string( const my_string& m ) : s_( m.s_ ) { std::cout << "my_string(const my_string&) " << s_ << std::endl; } my_string( my_string&& m ) noexcept : s_( m.s_ ) { std::cout << "my_string(my_string&&) " << s_ << std::endl; } ~my_string() { std::cout << "~my_string() " << s_ << std::endl; } }; // the proxy struct my_string_proxy { mutable my_string s_; // add all ctors needed to initialize my_string my_string_proxy( const char* s ) : s_( s ) {} }; // functions/methods should be overloaded // for the initializer list versions void insert( std::vector<my_string>& v, const std::initializer_list<my_string_proxy>& il ) { for( auto& e : il ) { v.push_back( e.s_ ); } } void insert( std::vector<my_string>& v, std::initializer_list<my_string_proxy>&& il ) { for( auto& e : il ) { v.push_back( std::move( e.s_ ) ); } } int main() { std::vector<my_string> words; insert( words, { {"Hello"}, {"initializer"}, {"with"}, {"move"}, {"support"} } ); } 

Live example

Comments

1

Instead of taking an initializer list, for the cost of a template, you can take an rvalue reference of array:

template<typename T, std::size_t n> void insert(std::vector<T>& vector, T(&&elements)[n]) { for (T& element : elements) vector.push_back(std::move(element)); } 

(The template parameter T is not necessary for the technique if you know the type, but n is.)

int main() { std::vector<std::unique_ptr<std::string>> strings; // C++14 onward: use std::make_unique insert(strings, { std::unique_ptr<std::string>{ new std::string{} }, std::unique_ptr<std::string>{ new std::string{"abc"} } }); } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.