There are several questions (like this) in SO about the impossibility of moving objects out of an initializer list and how that may lead to excessive copies. This question is not about that.
My question is, given that an initializer list is basically a container of locally valid const references supporting shallow copy, why does it causes copies of its (lvalue) elements on creation? Rvalues in an initializer list are not copied, just moved, but lvalues are indeed copied. Here is an example that shows it:
#include <initializer_list>
#include <iostream>
#include <vector>
struct A
{
A()
{
std::cout << "Creating A" << std::endl;
}
~A()
{
std::cout << "Deleting A" << std::endl;
}
A(const A &other)
{
std::cout << "Copying A" << std::endl;
}
A(A &&other)
{
std::cout << "Moving A" << std::endl;
}
A &operator=(const A &other)
{
std::cout << "Copy-assigning A" << std::endl;
return *this;
}
A &operator=(A &&other)
{
std::cout << "Move-assigning A" << std::endl;
return *this;
}
};
int main(int, char **)
{
A a;
std::cout << "Creating initializer list 1." << std::endl;
std::initializer_list<A> il1 { a };
std::cout << "Created initializer list 1." << std::endl;
std::vector<A> v {A(), A(), A()};
std::cout << "Creating initializer list 2." << std::endl;
std::initializer_list<std::vector<A>> il2 { v };
std::cout << "Created initializer list 2." << std::endl;
return 0;
}
And the output (with GCC 5.4 and Clang 3.8):
Creating A
Creating initializer list 1.
Copying A
Created initializer list 1.
Creating A
Creating A
Creating A
Copying A
Copying A
Copying A
Deleting A
Deleting A
Deleting A
Creating initializer list 2.
Copying A
Copying A
Copying A
Created initializer list 2.
Deleting A
Deleting A
Deleting A
Deleting A
Deleting A
Deleting A
Deleting A
Deleting A
The relevant parts are those between Creating...
and Created...
, where it can be seen that the initializer list produces copies of its arguments. Looking in cppreference, it says that (as part of C++14 specification) std::initializer_list<T>
holds internally an array const T[N]
, which would explain the copies. I don't understand why this is necessary. As suggested in the comments, a workaround is to use an initializer list of pointers or std::reference_wrapper
s, but wouldn't it be better if the internal structure were itself an array of pointers or std::reference_wrapper
s? As it is right now, it looks as if initializer lists were meant to be used with rvalues (or light objects), but at the same time they do not allow to move objects out of them. Is there some case where not copying the lvalues to an internal structure could be harmful? Or is it just to avoid one pointer dereferencing operation?