Stumped by codegen for std::initializer_list

Hi,

I'm kinda stuck at codegenning std::initializer_list and would appreciate if someone more experienced could give advice.

Basically, if you initialize a std::initializer_list object from an initializer list, like so:

std::initializer_list<int> il = { 1, 2, 3 };

we are supposed to create a hidden array int[3], initialize that with the init list, and then have the std::initlist point at the array. The lifetime of this hidden array (and its contained objects) is incorrectly specified at the moment, but is probably supposed to be the same as if it was a temporary that is bound to a reference, that reference being the initializer_list object.

In other words, for the above, the array has the same lifetime as il (automatic or static), whereas if the initializer_list is a temporary, so is the array:

void fn1(std::initializer_list<int>);
fn1({1, 2, 3}); // array (and elements) destroyed at end of full-expression

void fn2(std::vector<int>);
fn2({1, 2, 3}); // initializer_list created as an argument for the vector constructor, same lifetime.

new std::initializer_list<int> {1, 2, 3}; // array destroyed at end of full-expression (evil!)

My AST for this is just an InitListExpr containing the individual object expressions, wrapped in an ExprWithCleanups if the element type has a destructor (which is the interesting case).

So far I've tried to modify AggExprEmitter::VisitInitListExpr, marking init lists for std::initializer_list in the AST, and emitting the temporary array. However, I just don't see a way to get the lifetime right in this case.
My other idea is to modify CodeGenFunction::EmitReferenceBindingToInit to deal with std::initializer_list kind of like with references, but that doesn't seem like a particularly good approach either, and I don't see how I would avoid massive code duplication with the subexpression case.

So, where and how is the best way to modify CodeGen for this?

Sebastian

Does this apply recursively? i.e. if you have a std::initializer_list<std::initializer_list<std::string>>, should both levels of array be implicitly extended?

Also, what happens in cases of copy elision of the intiializer_list object? e.g.
  auto list = (std::initializer_list<std::string>) { "" };

I don't see any great solutions beyond special-casing the emission of local and global variables of std::initializer_list type, unless it's to create a special AST node or something (or a flag?) to mark the special kind of construction (which shouldn't be *too* difficult, since it's a special case of initialization already). That won't really help with the copy-elision case, though.

John.

Doug, CCing you explicitly here. Are you going to Kona? If so, can you ask for fast, unambiguous resolution of initializer_list-bound array lifetime? This is DR1290, which, on the public list, is just a title. See below.

I'm kinda stuck at codegenning std::initializer_list and would appreciate if someone more experienced could give advice.

Basically, if you initialize a std::initializer_list object from an initializer list, like so:

std::initializer_list<int> il = { 1, 2, 3 };

we are supposed to create a hidden array int[3], initialize that with the init list, and then have the std::initlist point at the array. The lifetime of this hidden array (and its contained objects) is incorrectly specified at the moment, but is probably supposed to be the same as if it was a temporary that is bound to a reference, that reference being the initializer_list object.

Does this apply recursively? i.e. if you have a std::initializer_list<std::initializer_list<std::string>>, should both levels of array be implicitly extended?

I have no idea. It feels like this should work.

Also, what happens in cases of copy elision of the intializer_list object? e.g.
   auto list = (std::initializer_list<std::string>) { "" };

I also have no idea. I think this still should destroy the init list at the end of the full-expression. On the other hand,
auto list = {1, 2, 3};
should not, and the code is semantically pretty much the same, I think.

Argh! The standard is so horribly underspecified here!

I don't see any great solutions beyond special-casing the emission of local and global variables of std::initializer_list type, unless it's to create a special AST node or something (or a flag?) to mark the special kind of construction (which shouldn't be *too* difficult, since it's a special case of initialization already). That won't really help with the copy-elision case, though.

OK, so I'll just have to do my best to factor out the code duplication. Thank you.

Doug: so we've got a few interesting examples for initializer_list lifetime issues:
auto list = {1, 2, 3}; // Conceptually, creates a temporary init_list and copies to the stack variable. Array would be gone at end of FE. Cannot be what is wanted.
auto list = std::initializer_list{1, 2, 3}; // Same as above, but explicit in the construction of the temporary.
new auto{1, 2, 3}; // Allocates the init_list on the heap. What about the array, which is supposed to have the same lifetime?
std::initializer_list<std::initializer_list<std::string>>{{"one"}, {"two"}}; // What's the lifetime of the arrays of the inner init_lists, and how on earth are we supposed to implement that? (Note to self: try in GCC.)
struct S { std::initializer_list<int> mList; S() : mList{1, 2, 3} {} }; // What about here?

Let's try to resolve as many of these as possible with the analogy to references.

struct D { D(int); ~D(); };
const D& r1 = {1}; // Binds to temporary, extends lifetime, very special rule though.
const D& r2 = (const D&){1}; // What does this do?
struct CR { const D& ref; };
struct CRR { const CR& ref; };
new CR{{1}}; // D temporary gets destroyed at end of FE.
CRR crr{{{1}}}; // What does this do? How many compilers get it right?
struct IR { const D& ref; IR() : ref{1} {} }; // D temporary destroyed at end of initializer.

Sebastian