Uniform intialization with std::intializer_list is not uniform.

512 views
Skip to first unread message

toma...@gmail.com

unread,
Aug 14, 2013, 6:50:47 AM8/14/13
to std-pr...@isocpp.org
The std::initializer_list<T> is defined as a non-owning handle to a const array of the T elements placed probably on stack, with requires from
 containers constructor to copy the elements into container. This lead for usability problem, especially in generic programming.

The most common example of usage of std::initializer_list is to write:
  std::vector<int> v{1,2,3,4};
instead of:
  std::vector<int> v; v.reserve(4);
  v.emplace_back(1);
  v.emplace_back(2);
  v.emplace_back(3);
  v.emplace_back(4);

There is not difference between this two codes, because the copy and move constructor of int does not differ.

The problem start to begin when we start to use the non-movable types (ex. locks). The following code:
  std::vector<std::unique_ptr<int>> v{ make_unique<int>(), make_unique<int>() };
Does not work because the copy constructor is required to copy elements from initializer_list and such
constructor does not exist for unique_ptr. So not all types of vector can be uniformly initialized.

Furthermore the use of the initializer list incur unnecessary performance penalty, please consider:
std::vector<std::string> v{ "ala", "ola", "ula" };
This initialization requires construction of the 6 string objects - 3 for initializer_list and 3 for copies in vector.

This 2 problems makes the usage of uniform intialization of container questionalable especially in generic code.


The problem itself make be solved by changing the initializer_list to be owning, non-copyable, non-movable handle for the
array. The the vector, should have 2 constructors:
  1. vector(std::initializer_list<T> cons& list)
  2. vector(std::initializer_list<T> && list)

The first one will be used only in situations when the intializer list was declared before, the second one will allow to move the
values of list element (instead of coping) to the container so the move-only objects will work, and performance penalty will be
lowered for std::string cases (3 constructor, 3 moves). The move of the elements will be safe because of the change in the semantic
of the list, to own the elements (non-other list depends of them).

Probably it is to late to make such a change, and the initializer_list will  not take move into account.

Daniel Krügler

unread,
Aug 14, 2013, 7:40:31 AM8/14/13
to std-pr...@isocpp.org
2013/8/14 <toma...@gmail.com>:
> The std::initializer_list<T> is defined as a non-owning handle to a const
> array of the T elements placed probably on stack, with requires from
> containers constructor to copy the elements into container. This lead for
> usability problem, especially in generic programming.
>
> The most common example of usage of std::initializer_list is to write:
> std::vector<int> v{1,2,3,4};
> instead of:
> std::vector<int> v; v.reserve(4);
> v.emplace_back(1);
> v.emplace_back(2);
> v.emplace_back(3);
> v.emplace_back(4);
> There is not difference between this two codes, because the copy and move
> constructor of int does not differ.
>
> The problem start to begin when we start to use the non-movable types (ex.
> locks). The following code:
> std::vector<std::unique_ptr<int>> v{ make_unique<int>(),
> make_unique<int>() };
> Does not work because the copy constructor is required to copy elements from
> initializer_list and such
> constructor does not exist for unique_ptr. So not all types of vector can be
> uniformly initialized.

This is a well-known characteristics of std:initializer_list and has
been discussed when considering the (late) proposals

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2719.pdf
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2801.pdf

IMO it is natural for an handle to immutable data not to allow invalid
mutating operations that would be required to be used for the
referenced values.

> Furthermore the use of the initializer list incur unnecessary performance
> penalty, please consider:
> std::vector<std::string> v{ "ala", "ola", "ula" };
> This initialization requires construction of the 6 string objects - 3 for
> initializer_list and 3 for copies in vector.

If this difference is relevant I recommend to stay away here from
std::initializer_list or to invent a new mutable initializer_list (see
proposals mentioned above).

> This 2 problems makes the usage of uniform intialization of container
> questionalable especially in generic code.

I agree, I would not suggest to use them in deeply generic code that
should impose as little constraints as possible on the value types of
the container.

> The problem itself make be solved by changing the initializer_list to be
> owning, non-copyable, non-movable handle for the
> array. The the vector, should have 2 constructors:
> 1. vector(std::initializer_list<T> cons& list)
> 2. vector(std::initializer_list<T> && list)

This wouldn't change anything, because it would still be invalid to
modify the referenced data via the second overload. Core issue

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1418

clarified that the underlying data of the std::initializer_list indeed
has to be considered as constant values and any modification of that
would be undefined behaviour.

> Probably it is to late to make such a change, and the initializer_list will
> not take move into account.

It clearly is too late and I recommend to work on updated proposals
similar to the direction of n2801 mentioned above.

- Daniel

Ville Voutilainen

unread,
Aug 14, 2013, 7:52:13 AM8/14/13
to std-pr...@isocpp.org
I wouldn't personally call it well-known, because it manages to surprise programmers when they
run into it, experts and novices alike, myself somewhere in that spectrum included.
 
> Probably it is to late to make such a change, and the initializer_list will
> not take move into account.

It clearly is too late and I recommend to work on updated proposals
similar to the direction of n2801 mentioned above.



I find it quite unfortunate if there's nothing that can be done for braced-initializers that
have move-only types in them, unique_ptr being a very good example, stringstreams being
another.

Daniel Krügler

unread,
Aug 14, 2013, 8:10:50 AM8/14/13
to std-pr...@isocpp.org
2013/8/14 Ville Voutilainen <ville.vo...@gmail.com>:
>> This is a well-known characteristics of std:initializer_list and has
>> been discussed when considering the (late) proposals
>
> I wouldn't personally call it well-known, because it manages to surprise
> programmers when they
> run into it, experts and novices alike, myself somewhere in that spectrum
> included.
> I find it quite unfortunate if there's nothing that can be done for
> braced-initializers that
> have move-only types in them, unique_ptr being a very good example,
> stringstreams being
> another.

It is certainly possible to host std::initializer_type<MoveOnly>. The
only (IMO natural) constraint is that you cannot modify the underlying
data. Is this really such a surprise?

#include <initializer_list>

struct MoveOnly
{
int value;
MoveOnly(int value) : value(value) {}
MoveOnly(MoveOnly&&) = default;
MoveOnly& operator=(MoveOnly&&) = default;
};

std::initializer_list<MoveOnly> mos{MoveOnly(1), MoveOnly(2)};

The request by the OP sounds to me that it seems unfortunate that
immutable data cannot be mutated. What is so special for
std::initializer_list to change this fundamental concept?

I'm not opposed to invent a new mutable initializer_list, but that
looks like a feature to me. From begin with the model of
std::initializer_list was intended to support constant data.

- Daniel

Ville Voutilainen

unread,
Aug 14, 2013, 8:17:43 AM8/14/13
to std-pr...@isocpp.org
It's not unfortunate that immutable data can't be mutated. What's unfortunate is that
one easily gets the impression that vector<int>{foo(), bar(), baz()} is supposed to allow
putting the contents of the vector right-into-it, to avoid having to write
vector<int> vec;
vec.push_back(foo());
vec.push_back(bar());
vec.push_back(baz());
but with move-only types we again have to fall back to that verbosity. If I reach further
into the issue, we can't initialize vectors and its ilk with move-only types. Since Resource
Acquisition Is Initialization, I find it potentially very unfortunate.

I think a solution like mutable_initializer_list is certainly a feature. Not being able to
initialize standard containers with move-only types certainly looks like a bug.

Daniel Krügler

unread,
Aug 14, 2013, 8:30:56 AM8/14/13
to std-pr...@isocpp.org
2013/8/14 Ville Voutilainen <ville.vo...@gmail.com>:
> It's not unfortunate that immutable data can't be mutated. What's
> unfortunate is that
> one easily gets the impression that vector<int>{foo(), bar(), baz()} is
> supposed to allow
> putting the contents of the vector right-into-it, to avoid having to write
> vector<int> vec;
> vec.push_back(foo());
> vec.push_back(bar());
> vec.push_back(baz());
> but with move-only types we again have to fall back to that verbosity. If I
> reach further
> into the issue, we can't initialize vectors and its ilk with move-only
> types. Since Resource
> Acquisition Is Initialization, I find it potentially very unfortunate.
>
> I think a solution like mutable_initializer_list is certainly a feature. Not
> being able to
> initialize standard containers with move-only types certainly looks like a
> bug.

The initializer_list constructor overloads were added to containers
and other types as an additional convenience, not as a replacement for
the iterator-based constructors or as a replacement for usage of
emplace. So to me it seems now that you are inverting the roles of
std::initializer_list constructors to the most fundamental ones.

I also cannot really share the interpretation, that RAII is not
possible for move-only types, but given any of the alternatives
mentioned above it seems still valid to me.

- Daniel

Gabriel Dos Reis

unread,
Aug 14, 2013, 8:45:58 AM8/14/13
to std-pr...@isocpp.org
Daniel Krügler <daniel....@gmail.com> writes:

| 2013/8/14 Ville Voutilainen <ville.vo...@gmail.com>:
| > It's not unfortunate that immutable data can't be mutated. What's
| > unfortunate is that
| > one easily gets the impression that vector<int>{foo(), bar(), baz()} is
| > supposed to allow
| > putting the contents of the vector right-into-it, to avoid having to write
| > vector<int> vec;
| > vec.push_back(foo());
| > vec.push_back(bar());
| > vec.push_back(baz());
| > but with move-only types we again have to fall back to that verbosity. If I
| > reach further
| > into the issue, we can't initialize vectors and its ilk with move-only
| > types. Since Resource
| > Acquisition Is Initialization, I find it potentially very unfortunate.
| >
| > I think a solution like mutable_initializer_list is certainly a feature. Not
| > being able to
| > initialize standard containers with move-only types certainly looks like a
| > bug.
|
| The initializer_list constructor overloads were added to containers
| and other types as an additional convenience, not as a replacement for
| the iterator-based constructors or as a replacement for usage of
| emplace.

Correct.

| So to me it seems now that you are inverting the roles of
| std::initializer_list constructors to the most fundamental ones.
|
| I also cannot really share the interpretation, that RAII is not
| possible for move-only types, but given any of the alternatives
| mentioned above it seems still valid to me.

Agreed.

I hope we're not too quick in reaching conclusions.

-- Gaby

Ville Voutilainen

unread,
Aug 14, 2013, 8:53:51 AM8/14/13
to std-pr...@isocpp.org
On 14 August 2013 15:30, Daniel Krügler <daniel....@gmail.com> wrote:
The initializer_list constructor overloads were added to containers
and other types as an additional convenience, not as a replacement for
the iterator-based constructors or as a replacement for usage of

Yet they are able to do things iterator constructors or emplace cannot do.
 
emplace. So to me it seems now that you are inverting the roles of
std::initializer_list constructors to the most fundamental ones.

Uh, ok. I didn't realize I was in the field of such role inversion, I certainly
didn't intend to be.
 

I also cannot really share the interpretation, that RAII is not
possible for move-only types, but given any of the alternatives
mentioned above it seems still valid to me.

The alternatives above aren't applicable, since to use an iterator
constructor, I'd need to have something iterable. And emplace isn't
initialization. 

Jonathan Wakely

unread,
Aug 14, 2013, 9:13:26 AM8/14/13
to std-pr...@isocpp.org

On Wednesday, August 14, 2013 1:53:51 PM UTC+1, Ville Voutilainen wrote:


I also cannot really share the interpretation, that RAII is not
possible for move-only types, but given any of the alternatives
mentioned above it seems still valid to me.

The alternatives above aren't applicable, since to use an iterator
constructor, I'd need to have something iterable. And emplace isn't
initialization. 

It's not tooooo hard to make it into something iterable, that can be moved from:

MoveOnly arr[] = { foo(), bar(), baz() };
std::vector<MoveOnly> v(std::make_move_iterator(std::begin(arr)), std::make_move_iterator(std::end(arr)));
 

Daniel Krügler

unread,
Aug 14, 2013, 9:19:19 AM8/14/13
to std-pr...@isocpp.org
2013/8/14 Ville Voutilainen <ville.vo...@gmail.com>:
1) Since initializer_list constructor of constainers are already a
kind of two-step initialization (Allocate an array of some const
stuff, let std::initializer_list point to it and then iterate over the
std::initializer_list range), this scheme can be rewritten acting upon
non-constant data using e.g. std::move_iterator to provide the data to
the constructor of the container. Instead of a single-liner this can
be two-liner (if expanded and not wrapped into a function).

MoveOnly data[] = { .... };
std::vector<MoveOnly> v(std::make_move_iterator(std::begin(data)),
std::make_move_iterator(std::end(data)));

(I see now that Jonathan proposes the same strategy)

If this is not good enough, it should be possible to write a function
template of the form

template<class T, std::size_t N>
std::move_iterator<T> move_away(T (&& t)[N]);

and wait for the expect range-support to allow something like:

std::vector<MoveOnly> v(move_away({MoveOnly(1), MoveOnly(2)}));

[untested]

2) Alternatively you can create a local container, emplace the
elements, then move this container into the actual target container
(typically all this is provided by a single function template).

- Daniel

Ville Voutilainen

unread,
Aug 14, 2013, 10:03:18 AM8/14/13
to std-pr...@isocpp.org
On 14 August 2013 16:13, Jonathan Wakely <c...@kayari.org> wrote:

The alternatives above aren't applicable, since to use an iterator
constructor, I'd need to have something iterable. And emplace isn't
initialization. 

It's not tooooo hard to make it into something iterable, that can be moved from:

MoveOnly arr[] = { foo(), bar(), baz() };
std::vector<MoveOnly> v(std::make_move_iterator(std::begin(arr)), std::make_move_iterator(std::end(arr)));


Thanks. That's not toooo hard, but I would rather not encourage people to have to use
raw arrays or even std::array to achieve something that's arguably fundamental. Perhaps
it's just me. </bitch bitch whine>. :)  Arguably.

Ville Voutilainen

unread,
Aug 14, 2013, 10:06:31 AM8/14/13
to std-pr...@isocpp.org
On 14 August 2013 16:19, Daniel Krügler <daniel....@gmail.com> wrote:

If this is not good enough, it should be possible to write a function
template of the form

template<class T, std::size_t N>
std::move_iterator<T> move_away(T (&& t)[N]);

and wait for the expect range-support to allow something like:

std::vector<MoveOnly> v(move_away({MoveOnly(1), MoveOnly(2)}));

[untested]

2) Alternatively you can create a local container, emplace the
elements, then move this container into the actual target container
(typically all this is provided by a single function template).



As working as these work-arounds may be, they are still work-arounds. At any rate,
I think it would be worthwhile to explore whether we can do a fix that doesn't require
such work-arounds. The "wait for the expected range-support" rings an alarm bell
in my head. The second option requires as much if not more code written as
the push_back/emplace-separately.

Daniel Krügler

unread,
Aug 14, 2013, 10:39:16 AM8/14/13
to std-pr...@isocpp.org
2013/8/14 Ville Voutilainen <ville.vo...@gmail.com>:
>
> On 14 August 2013 16:19, Daniel Krügler <daniel....@gmail.com> wrote:
>>
>> If this is not good enough, it should be possible to write a function
>> template of the form
>>
>> template<class T, std::size_t N>
>> std::move_iterator<T> move_away(T (&& t)[N]);
>>
>> and wait for the expect range-support to allow something like:
>>
>> std::vector<MoveOnly> v(move_away({MoveOnly(1), MoveOnly(2)}));
>>
>> [untested]
>>
>> 2) Alternatively you can create a local container, emplace the
>> elements, then move this container into the actual target container
>> (typically all this is provided by a single function template).
>>
>>
> As working as these work-arounds may be, they are still work-arounds. At any
> rate,
> I think it would be worthwhile to explore whether we can do a fix that
> doesn't require such work-arounds. The "wait for the expected range-support" rings an alarm
> bell in my head.

I don't understand why. Are you against range-support? IMO
std::initializer_list constructors in the library are just a special
case of a more generalized range support pattern and I find it more
reasonable to look for a more general solution instead of trying to
mutate std::initializer_list to something that it was not intended to
be.

> The second option requires as much if not more code written as
> the push_back/emplace-separately.

It's your choice ;-) (But I doubt the premise)

- Daniel

Ville Voutilainen

unread,
Aug 14, 2013, 12:50:18 PM8/14/13
to std-pr...@isocpp.org
On 14 August 2013 17:39, Daniel Krügler <daniel....@gmail.com> wrote:
> I think it would be worthwhile to explore whether we can do a fix that
> doesn't require such work-arounds. The "wait for the expected range-support" rings an alarm
> bell in my head.

I don't understand why. Are you against range-support? IMO

No. I'm against lumping every problem we have under the range umbrella.
string::split being one of them, this move-from-braced-list being another.
I don't think ranges are the proper solution for such things.
 
std::initializer_list constructors in the library are just a special
case of a more generalized range support pattern and I find it more
reasonable to look for a more general solution instead of trying to
mutate std::initializer_list to something that it was not intended to
be.

That's ok, I don't know what the solution should be yet, perhaps
mutating initializer_list isn't it. The problem shouldn't be "this is how
initializer_lists work, don't expect braced-initializers to work differently".
I'm not supposed to KNOW there's an initializer_list involved when
I construct a vector from a braced list. At least I'm under the impression
that initializer_list was supposed to be implementation plumbing
rather than something that's in-my-face. And again, I don't think
that implementation plumbing works quite right if I can't braced-init
with move-only types. Other people can freely agree to disagree
with that.
 


toma...@gmail.com

unread,
Aug 14, 2013, 4:42:38 PM8/14/13
to std-pr...@isocpp.org


That's ok, I don't know what the solution should be yet, perhaps
mutating initializer_list isn't it. The problem shouldn't be "this is how
initializer_lists work, don't expect braced-initializers to work differently".
I'm not supposed to KNOW there's an initializer_list involved when
I construct a vector from a braced list. At least I'm under the impression
that initializer_list was supposed to be implementation plumbing
rather than something that's in-my-face. And again, I don't think
that implementation plumbing works quite right if I can't braced-init
with move-only types. Other people can freely agree to disagree
with that.
 
The std::initializer_list<int> is only the solution of the uniform initialization problem,
not the problem itself. So while the design of initializer list is perfectly valid (thanks
Daniel for the papers), this model failed to achieve the main goal:

Provide uniform initialization for build-in types (arrays) and
user defined types.

That is the slogan/promotional phase for the most of materials about C++11.
But the initialization of the vector is not uniform with the build-in arrays, which
is suprising for the users and make learning of C++ harder, not easer as
uniform initialization suppose to. Especially when no presentation/material know to me
on C++11 mentions this - they use the slogan.

Was another approach possible?
I think yes. To point is to not use 2-face initialization and use varidatic templates instead:

template<typename T>
inline static_for_init(T* data) {}

template<typename T, typename U, typename... Us>
inline static_for_init(T* data, U&& u, Us&& us)
{
  new(data) T{std::forward<U>(u)}; //the allocator should be used here, but this will show idea
  static_for_init(data++, std::forward<Us>(us)...);
}

template<typename T>
struct vector
{
  template<typename... Us>
  vector(Us&&... us)
  //some SFINAE on type of arguments.
    : _data(sizeof...(Us)),
      _size(sizeof...(Us))
  {  static_for_init(_data, std::forward<Us>(us)); }
private:
  T* _data;
  std::size_t _size;
};

Of course this code needs allocator support (std::allocator_arg should be used), but this
is doable.

This will of course make writing an STL-like container even harder, but the allocator support
and exception safety already make this a really hrd task so that should not be a problem. Also
there will be some problem with ambiguity of constructor. But from the user perspective the goal will
be achived - the vector intialization will be uniform with intialization of build in arrays, even considering
performance.

Summary:
At this moment this cannot be changed, as the C++11 standard was shipped. I written my first post
to emphasize the problem, because I was aware of the problem (I am used to read the standard to check new things),
but non of my co-workers was aware of it.

Also it is a bit sad, that the solution choosen (std::intializer_list) to address problem (aggreate intialization) lead us to not
fulfilling the goal.

Nicol Bolas

unread,
Aug 14, 2013, 7:59:13 PM8/14/13
to std-pr...@isocpp.org
On Wednesday, August 14, 2013 1:42:38 PM UTC-7, toma...@gmail.com wrote:
That's ok, I don't know what the solution should be yet, perhaps
mutating initializer_list isn't it. The problem shouldn't be "this is how
initializer_lists work, don't expect braced-initializers to work differently".
I'm not supposed to KNOW there's an initializer_list involved when
I construct a vector from a braced list. At least I'm under the impression
that initializer_list was supposed to be implementation plumbing
rather than something that's in-my-face. And again, I don't think
that implementation plumbing works quite right if I can't braced-init
with move-only types. Other people can freely agree to disagree
with that.
 
The std::initializer_list<int> is only the solution of the uniform initialization problem,
not the problem itself. So while the design of initializer list is perfectly valid (thanks
Daniel for the papers), this model failed to achieve the main goal:

Provide uniform initialization for build-in types (arrays) and
user defined types.

No. Uniform initialization was about the copy vs. direct vs. aggregate initialization paradigm. That is, providing a single way to initialize a type that works exactly the same in all circumstances, which can be used for all types. But because it used {} syntax, it was also adopted into providing a way to initialize an arbitrary object from a sequence of equivalently typed parameters. But that wasn't its primary purpose.

That is the slogan/promotional phase for the most of materials about C++11.
But the initialization of the vector is not uniform with the build-in arrays, which
is suprising for the users and make learning of C++ harder, not easer as
uniform initialization suppose to. Especially when no presentation/material know to me
on C++11 mentions this - they use the slogan.
 
That's because it's fairly minor, all things considered. The far more eggregious issue with UI is that initializer_list constructors can hide constructors that just so happen to take the same types of parameters. The fact that you can't put a non-copyable type in a braced-init-list that will be converted into an initializer_list is pretty minor next to hiding important constructors, thus forcing you back into () constructor syntax.
 
Was another approach possible?
I think yes. To point is to not use 2-face initialization and use varidatic templates instead:

Um, how does that help? It now turns something that was quite simple into something incredibly complex. Yes, it solves one problem, but it creates many more.

It makes accidental narrowing very possible. It makes the compiler errors you get for creating a braced-init-list of different types much more oddball. It causes the generation of lots of pointless code, bloating compile times needlessly (it's a list of known size. We shouldn't have to template metaprogram recurse through it). It also conflicts with other parts of uniform initialization: being able to call non-initializer_list constructors with braced-init-lists. Lastly, there's no way to differentiate between a variadic template intended for some other purpose and an object that you want to be constructed from an arbitrary list of values.
 
Of course this code needs allocator support (std::allocator_arg should be used), but this
is doable.

This will of course make writing an STL-like container even harder, but the allocator support
and exception safety already make this a really hrd task so that should not be a problem. Also
there will be some problem with ambiguity of constructor. But from the user perspective the goal will
be achived - the vector intialization will be uniform with intialization of build in arrays, even considering
performance.

There are a lot more uses for having an object with an initializer_list constructor than just an STL-compliant container type. We shouldn't make you have to go through that much effort to process an array.

As people have said, this is a solveable problem. We may need a new "initializer_list" type to solve it, but it can certainly be resolved under the existing paradigm.

David Krauss

unread,
Aug 14, 2013, 9:35:58 PM8/14/13
to std-pr...@isocpp.org
On Thursday, August 15, 2013 7:59:13 AM UTC+8, Nicol Bolas wrote:
There are a lot more uses for having an object with an initializer_list constructor than just an STL-compliant container type. We shouldn't make you have to go through that much effort to process an array.
As people have said, this is a solveable problem. We may need a new "initializer_list" type to solve it, but it can certainly be resolved under the existing paradigm.

What tools or flexibilities are available to solve within the existing paradigm? The really unfortunate thing is not that the initial solution fell short, but that the revision is already too late for C++14?

Has there been any preliminary study of what might be changed and what is really essential as-is in the std::initializer_list spec?

For example, so much would be fixed if begin() and end() could return non-const pointers. Is this a serious breaking change per se? It doesn't seem so to me.

A good start would be to differentiate between const and non-const std::initializer_list. A function taking a const std::initializer_list by value would have read-only access to its contents, and allow the implementation to use storage in ROM, whereas dropping the const would force it into stack allocation.

Although pass-by-const-value is generally useless, a quick trial in GCC shows that you're already allowed to overload const and non-const parameters (i.e. const does not decay away in a function parameter type), so the implementation might not be so bad.

Richard Smith

unread,
Aug 14, 2013, 11:29:53 PM8/14/13
to std-pr...@isocpp.org
I agree. vector<unique_ptr<T>> can't be brace-initialized, so we have not fully arrived at the clean, simple, uniform initialization syntax we wanted. initializer_list<T> is supposed to be the vehicle for communicating a braced-init-list from an initialization to a constructor, and it's not fully succeeding at that task.

The core of the problem seems to be that initializer_list<T> does not own its elements, not that the elements are immutable. If we merely made the contents of the initializer_list mutable, we would break move-safety:

initializer_list<unique_ptr<T>> x = { a, b, c };
auto get() { return x; }
std::vector<unique_ptr<T>> v(get()); // oops, implicitly move here

We could fix this by adding a move-only std::unique_initializer_list<T> (or similar) that owns its elements, but it seems overly-complicated to have two similar-but-different initializer_list types in the library. Conversely, it's probably too late to fix initializer_list to own its contents (and thus be move-only).

Gabriel Dos Reis

unread,
Aug 15, 2013, 12:02:10 AM8/15/13
to std-pr...@isocpp.org
I agree with some of the descriptions here. However, I have to wonder
whether we are in a classical configuration of 'perfect is the enemy of good'.

If we get 95% right, then realistically, I consider we've done a good job.

Initialization in C++ is a multi-faceted and treacherous problem -- from
what I've learned working on this problem for a very long period of time.
It is very easy to get one facet perfect, 100% right. Putting all the
perfect solutions to the isolated problems together is a highly
non-trivial whack-a-mole. My conjecture is, somewhere we are going to
make a community or an expert unhappy about one resolution or the other.

Even as of today, I still consider the current solution acceptable.
It does not mean that I believe we can't make improvement though -- but
I just think that any substantial improvement from now is going to
result in complication for some segments of users.

-- Gaby

David Krauss

unread,
Aug 15, 2013, 1:58:56 AM8/15/13
to std-pr...@isocpp.org


On Thursday, August 15, 2013 11:29:53 AM UTC+8, Richard Smith wrote:
The core of the problem seems to be that initializer_list<T> does not own its elements, not that the elements are immutable. If we merely made the contents of the initializer_list mutable, we would break move-safety:

initializer_list<unique_ptr<T>> x = { a, b, c };
auto get() { return x; }
std::vector<unique_ptr<T>> v(get()); // oops, implicitly move here

Presuming this is supposed to go inside a class, this example is nonsense. An initializer_list cannot meaningfully be a member because it doesn't own its contents.

The problem with mutability is moderated by the limited set of valid use-cases where the initializer_list could potentially be accessed twice. Usually its lifetime is that of a temporary used as a function argument. Its contents are copied (often moving would be preferable) into something, then it dies. Most attempts to generalize into broader use are fraught with danger, and should simply be disallowed/deprecated (and opened to future redefinition).

As a band-aid to my above idea, I would propose that lvalue-to-rvalue conversion on an initializer_list produce a const rvalue (for lvalues, not xvalues). Then you can modify the elements in local scope but passing it by value (as is canonical) to a function does not confer write permission. But really the mutability proposal is just a distraction.

My intended point is that we need to organize ideas and decide what initializer_list really needs to do, what we can change and what we cannot. Then the debate can really be advanced. And maybe we can even outlaw surprisingly-invalid use cases, such as declaring one to be a class member, in time for C++14.

Gabriel Dos Reis

unread,
Aug 15, 2013, 2:33:46 AM8/15/13
to std-pr...@isocpp.org
David Krauss <pot...@gmail.com> writes:

| My intended point is that we need to organize ideas and decide what
| initializer_list really needs to do, what we can change and what we cannot.

I am pretty sure we did that when we designed (and considered alternatives)
for uniform initialization and std::initializer_list.

-- Gaby

toma...@gmail.com

unread,
Aug 15, 2013, 3:12:09 AM8/15/13
to std-pr...@isocpp.org
No, the constructor code itself brace-initialized constructor for every object, so the some narrowing rules will apply.

It makes the compiler errors you get for creating a braced-init-list of different types much more oddball.
The constructor can be SFINAE'd. The the user will see that there is no viable constructor to construct vector<int> from [char, string].
 
It causes the generation of lots of pointless code, bloating compile times needlessly (it's a list of known size. We shouldn't have to template metaprogram recurse through it).
The size of every varidatic parameters packs is know, so your argument apply's also to them, so this is a problem with lack of imperative iteration over parameters pack, not initialization per-se.

It also conflicts with other parts of uniform initialization: being able to call non-initializer_list constructors with braced-init-lists. Lastly, there's no way to differentiate between a variadic template intended for some other purpose and an object that you want to be constructed from an arbitrary list of values
 
But you make a comment in this post that this is not such good from user-prespective, to have such diffrenecation in class.


 
Of course this code needs allocator support (std::allocator_arg should be used), but this
is doable.

This will of course make writing an STL-like container even harder, but the allocator support
and exception safety already make this a really hrd task so that should not be a problem. Also
there will be some problem with ambiguity of constructor. But from the user perspective the goal will
be achived - the vector intialization will be uniform with intialization of build in arrays, even considering
performance.

There are a lot more uses for having an object with an initializer_list constructor than just an STL-compliant container type. We shouldn't make you have to go through that much effort to process an array.

The ignore pattern may be also used.
struct ignore
{
   template<typename Ts>
   ignore(Ts&&...) {}
};

template<typename... Us>
vector(Us&&... us)
//some SFINAE on type of arguments.
  : _data(sizeof...(Us)),
    _size(sizeof...(Us))
{  ignore{new(_data++) T{us}....}; } //order of evalution is left-to-right.

As people have said, this is a solvable problem. We may need a new "initializer_list" type to solve it, but it can certainly be resolved under the existing paradigm.

If the mutable_initializer_list has the same problem, then how you will choose constructor to use if class have both of them? If they will be ambiguous then one of them mus be SFINAE'd for non-movable/movable objects, so if we want the class to accept an list in constructor, then that will be still non-tribal.

Richard Smith

unread,
Aug 15, 2013, 6:51:21 AM8/15/13
to std-pr...@isocpp.org
On Wed, Aug 14, 2013 at 10:58 PM, David Krauss <pot...@gmail.com> wrote:


On Thursday, August 15, 2013 11:29:53 AM UTC+8, Richard Smith wrote:
The core of the problem seems to be that initializer_list<T> does not own its elements, not that the elements are immutable. If we merely made the contents of the initializer_list mutable, we would break move-safety:

initializer_list<unique_ptr<T>> x = { a, b, c };
auto get() { return x; }
std::vector<unique_ptr<T>> v(get()); // oops, implicitly move here

Presuming this is supposed to go inside a class, this example is nonsense.

Sure, if you made a weird assumption, the example would be meaningless. Those were top-level declarations (and just a demonstration of the problem -- there are plenty of other ways to arrive at the same issue).

David Krauss

unread,
Aug 15, 2013, 7:42:37 PM8/15/13
to std-pr...@isocpp.org

For some reason my mental compiler overlooked namespace scope. Surely there are many minor variations, but similarly silly.

Is there any practical use case of declaring initializer_list to use its implicit array as a persistent container? Would it be such a bad thing to simply disallow defining one except as a function argument?

As it is, initializer_list supports the usual syntax of value-semantic objects without supporting the semantics. As the language gets richer, it only becomes easier to fall into a trap. Is this valid C++14?

auto f() { return { 1, 2, 3 }; } // dangling reference!

This looks temptingly idiomatic. And it's similar to the very first thing I ever tried to do with initializer_list: to return a list from a function. (Which C++11 erroneously specified as being possible.)

Whatever functionality was agreed upon previously, its interface currently overextends its capabilities. We should fix it or deprecate it for future semantic redefinition.

David Krauss

unread,
Aug 15, 2013, 7:45:34 PM8/15/13
to std-pr...@isocpp.org


On Friday, August 16, 2013 7:42:37 AM UTC+8, David Krauss wrote:

Whatever functionality was agreed upon previously, its interface currently overextends its capabilities. We should fix it or deprecate it for future semantic redefinition.

Sorry, the latter "it" referring to things like returning from a function, declaration as a class member, and perhaps any declaration outside a parameter list. Not deprecation of the whole class.

Ville Voutilainen

unread,
Aug 15, 2013, 7:46:03 PM8/15/13
to std-pr...@isocpp.org
On 16 August 2013 02:42, David Krauss <pot...@gmail.com> wrote:
As it is, initializer_list supports the usual syntax of value-semantic objects without supporting the semantics. As the language gets richer, it only becomes easier to fall into a trap. Is this valid C++14?

auto f() { return { 1, 2, 3 }; } // dangling reference!

It's ill-formed because it would indeed create a dangling reference.

Zhihao Yuan

unread,
Aug 15, 2013, 7:53:12 PM8/15/13
to std-pr...@isocpp.org
On Wed, Aug 14, 2013 at 9:19 AM, Daniel Krügler
<daniel....@gmail.com> wrote:
> MoveOnly data[] = { .... };
> std::vector<MoveOnly> v(std::make_move_iterator(std::begin(data)),
> std::make_move_iterator(std::end(data)));

What I don't understand is, why we need initializer_list? Variadic
template can handle any case:

template <typename V, typename T1>
inline void back_pusher(V& v, T1 t1)
{
v.push_back(std::move(t1));
}

template <typename V, typename T1, typename... T2>
inline void back_pusher(V& v, T1 t1, T2... t2)
{
v.push_back(std::move(t1));
back_pusher(v, std::move(t2)...);
}

template <typename... T>
inline auto make_vector(T... t)
-> std::vector<std::common_type_t<T...>>
{
std::vector<std::common_type_t<T...>> v;
back_pusher(v, std::move(t)...);
return v;
}

so that you can use:

auto v = make_vector(std::make_unique<int>(3),
std::make_unique<int>(12));

or

std::string s = "lvalue";
auto v = make_vector("rvalue", s);

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
___________________________________________________
4BSD -- http://4bsd.biz/

David Krauss

unread,
Aug 15, 2013, 8:01:42 PM8/15/13
to std-pr...@isocpp.org


On Friday, August 16, 2013 7:53:12 AM UTC+8, Zhihao Yuan wrote:
On Wed, Aug 14, 2013 at 9:19 AM, Daniel Krügler
<daniel....@gmail.com> wrote:
> MoveOnly data[] = { .... };
> std::vector<MoveOnly> v(std::make_move_iterator(std::begin(data)),
> std::make_move_iterator(std::end(data)));

What I don't understand is, why we need initializer_list?  Variadic
template can handle any case:

Variadic templates are templates and each unique arity requires another specialization. One initializer_list function can be exported from a link library and yet handle any number of arguments.

It's a noble goal to improve passing arrays.

---


On Friday, August 16, 2013 7:46:03 AM UTC+8, Ville Voutilainen wrote:
No, it's not valid, see http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3638.html
It's ill-formed because it would indeed create a dangling reference.

So automatic deduction cannot result in invalid semantics, but explicit specification can. Sucks for users with more explicit coding style.

toma...@gmail.com

unread,
Aug 20, 2013, 7:29:19 AM8/20/13
to std-pr...@isocpp.org
I would like to present for the consideration and the feedback idea of owning_initializer_list.

Semantics:
The owning_intializer_list<T> should act as a wrapper for a temporary array that is:
  - non-copyable/non-movable, that means can only by passed by reference
  - owns the elements of temporary array

Usage:
As the owning_intializer_list<T> can be only passed by reference, the usage of this structure in containers would look like:
  - owning_initializer_list<T> const&
    The access to the element would be const, so the elements of the array must be copied
  - owning_intializer_list<T>&&
    The elements of the intializer list may be moved
The both constructor will be used depended of how initializer list is created and what kind of elements are stored
std::vector<std::string> v{"a", "b", "c"} - will use && overide
While:
const std::owning_intializer_list<std::string> l{"a", "b", "c"};
std::vector<std::string> v{l}; //will use const& overide

Explicit ownership/lifetime semantics:
Current implementation of the std::initializer_list<T> extends the lifespan of the temporary array the same way as binding the
temporary to reference that means, that for:
struct A
{
  A(
const int (& _t)[5])
    : t{_t} {}

  const int (& t)[5];
};

struct B
{
  B(
std::initializer_list<int> _t)
    : t{_t} {}

  std::initializer_list<int> t;
};

A{{1, 2, 3, 4, 5}};
B{1, 2, 3, 4, 5};
Both A and B constructor will create a dangling reference to the temporary array. So this only add one
more exception to remember for the programmers that want to use the language. The proposed
owning_initializer_list<T> does not require such case, because the reference must be used and
naturally the same lifespan expansions rules will apply, without any exceptions.
The std::initializer_list<int> has reference semantics while value syntax is used so this
may be error prone. The owning_initializer_list is designed to be non-copyable/movable for that
reason.


Optimization:
As I understand the immutable semantics of the std::initializer_list<int> allow
the compiler to perform optimization of the storage of initializer_list.
So instead of transforming:

std::vector<int> v{1, 2, 3, 4};
To:
const _temp[] {1, 2, 3, 4};
std::vector<v> v{std::initializer_list<int>{begin(_temp), end(_temp)}};
That may be transformed to:
static const _temp[] {1, 2, 3, 4};
std::vector<v> v{std::initializer_list<int>{begin(_temp), end(_temp)}};

So the temp object is constructed one and may be stored in ROM memory.

As I understand this kind of optimization can be only preformed if the side effects of the construction
of elements of array are not visible to the user, so that means that it is only applicable for types with
trivially constructible and for such kind of types there will be no benefit from using moves. So as I understand
the:
std::vector<std::string> v{"ala", "ola", "ula"};
Cannot be optimized that way, because of user provided new?

The same optimization may be applied for owning_intializer_list<T> if T is trivially constructible and there is const&
version.
In the case of the
std::vector<int> v{1, 2, 3, 4, 5};
The const reference overloaded may be invoked for the vector constructor and the temporary array my created
statically.
For the non-trivial types:
std::vector<std::string> v{"ala", "ola", "ula"};
The static storage optimization may not  be used, but the && constructor will be used and move will be possible.
And of course:
std::vector<std::unique_ptr<int>> v{ make_unique<int>(), make_unique<int>() };


Drawbacks:
To allow both optimizations and move I have introduced another exception, that for:
template<typename T>
void f(owning_initializer_list<T> const&);
template<typename T>
void f(owning_initializer_list<T>&&);

f({T{}, T{}, T{}});
May result in invocation of const& version if the T is trivially constructible.

std::intializer_list<T> and std::owning_initializer_list<T>:
I think i would be enough to define constructor of std::initializer_list<T> as
const reference to std::owning_initializer_list<T> cons& with the same semantics
as initializer_list<T> has for array now.

Summary:
The owning_initializer_list<T> seems to merge benefits of initializer_list and the moving semantics without
losing any benefits. Before going further will the ideas how to introduce it to the standard without breking
currency code, I would like you to ask about your opinion. Am I missing some functionalities of std::initializer_list
or making invalid assumptions.

David Krauss

unread,
Aug 21, 2013, 2:07:38 AM8/21/13
to std-pr...@isocpp.org
On Tuesday, August 20, 2013 7:29:19 PM UTC+8, toma...@gmail.com wrote:
std::intializer_list<T> and std::owning_initializer_list<T>:
I think i would be enough to define constructor of std::initializer_list<T> as
const reference to std::owning_initializer_list<T> cons& with the same semantics
as initializer_list<T> has for array now.


I like the idea of making std::initializer_list an alias template for a const-qualified version of something else. std::initializer_list behaves as if it were const-qualified when it is not, and no unqualified instance should really be allowed to exist.

What's the point of non-movability?

Aside from that, I think the "owning" part is a misnomer if it remains an accessor to an external "temporary" array. This kind of ownership doesn't work as a return value or class member. (Return value use is possible for a non-copyable, non-movable type if the return value is list-initialized and bound to a reference.) It all sounds more like a proposal for mutability, with restrictions that cover some but not all of the potential abuses of initializer_list, and in an entirely new supplemental class instead of addressing initializer_list directly.

toma...@gmail.com

unread,
Aug 21, 2013, 3:06:45 AM8/21/13
to std-pr...@isocpp.org

The point of non-movabilty and the ownership semantics is to disallow the use of owning_initlizer_list (by value) in return type or as the class member, which results in dangling reference, the dynarray<T> should be used in such situations.

The idea is to allow the object of owning_initializer_list to be construed only by compiler (not user). That enforces storing it in class or returning via reference and this  fall backs to already know cases: do not return reference to local object, do not store reference to temporary in class. Just remove one additional exception from language.

David Krauss

unread,
Aug 21, 2013, 4:52:44 AM8/21/13
to std-pr...@isocpp.org
On Wednesday, August 21, 2013 3:06:45 PM UTC+8, toma...@gmail.com wrote:
The point of non-movabilty and the ownership semantics is to disallow the use of owning_initlizer_list (by value) in return type or as the class member, which results in dangling reference, the dynarray<T> should be used in such situations.

The idea is to allow the object of owning_initializer_list to be construed only by compiler (not user). That enforces storing it in class or returning via reference and this  fall backs to already know cases: do not return reference to local object, do not store reference to temporary in class. Just remove one additional exception from language.

A noble goal, but it is not accomplished by non-movability. As I mentioned, you can use a non-movable return value by binding it to a reference. And non-movability does nothing to hinder class membership.

http://ideone.com/sK79kR

struct nm {
    nm( int v ) {}
    
    nm( nm const & ) = delete; // non-copyable
    nm( nm && ) = delete; // non-movable
};
 
struct qc {
    nm m{ 5 }; // direct-initialization of class member
} o;
 
nm qf() {
    return { 5 }; // direct-initialization of object in calling scope
}
 
void f() {
    nm && qo = qf(); // initialize a persistent local
}

I think the best solution to prevent use in a function return or class member is simply to explicitly forbid them. Besides being semantically straightforward, such a policy suggests that implementations can honestly give a diagnostic message simply saying "initializer_list cannot be used here; try dynarray instead."

Also, just to note again, you have not proposed ownership semantics in the usual C++ sense.
Message has been deleted

toma...@gmail.com

unread,
Aug 21, 2013, 2:30:29 PM8/21/13
to std-pr...@isocpp.org


W dniu środa, 21 sierpnia 2013 10:52:44 UTC+2 użytkownik David Krauss napisał:
On Wednesday, August 21, 2013 3:06:45 PM UTC+8, toma...@gmail.com wrote:
The point of non-movabilty and the ownership semantics is to disallow the use of owning_initlizer_list (by value) in return type or as the class member, which results in dangling reference, the dynarray<T> should be used in such situations.

The idea is to allow the object of owning_initializer_list to be construed only by compiler (not user). That enforces storing it in class or returning via reference and this  fall backs to already know cases: do not return reference to local object, do not store reference to temporary in class. Just remove one additional exception from language.

A noble goal, but it is not accomplished by non-movability. As I mentioned, you can use a non-movable return value by binding it to a reference. And non-movability does nothing to hinder class membership.

Actually the goal can be achieved by combination of non-transferable owning semantics.

The expression {a, b, c, d} in context where some intializer_list context is available, should be considered as the expression creating object of unnamed type in a similar way that lambda expression creates closure.
The unnamed object should be publicly derived from owning_initializer_list<T> type, and the should be non-copyable, non-movable, non-constructible, so only the constructor can create such kind of objects.

The created unnamed type may look like (but it should be left to implementation):
struct _unnamed : owning_initializer_list<T>
{
private:
  T elements[N]; //would actualy own elements
};

So the expression:
std::owning_initializer_list<T> v{1,2,3,4};
Would be interpreted as;
_unnamed<T> a;
std::owning_initializer_list<T> v{a};
And wont compile because owning_initializer_list is non-transferable, so no slicing will be possible.

You will be able to pass this object to the finction via reference on "store" it locally by using the life of temporary extensions:
std::owning_initializer_list<T>&& v{1,2,3,4}; //no slicing is done here

This will disallow are misuses of that object, by disallowing storing it by value anywhere, but in the context where this was well defined you can still declare it before use.
Your examples will be reduced to store by reference, and all exceptions be removed from language.

Also will this approach the std::initializer_list<T> would be assignable version of std::owning_initializer_list<T> const&;

inkwizyt...@gmail.com

unread,
Aug 23, 2013, 1:14:50 PM8/23/13
to std-pr...@isocpp.org


On Wednesday, August 14, 2013 12:50:47 PM UTC+2, toma...@gmail.com wrote:
The std::initializer_list<T> is defined as a non-owning handle to a const array of the T elements placed probably on stack, with requires from
 containers constructor to copy the elements into container. This lead for usability problem, especially in generic programming.

The most common example of usage of std::initializer_list is to write:
  std::vector<int> v{1,2,3,4};
instead of:
  std::vector<int> v; v.reserve(4);
  v.emplace_back(1);
  v.emplace_back(2);
  v.emplace_back(3);
  v.emplace_back(4);

There is not difference between this two codes, because the copy and move constructor of int does not differ.

It can be easy fixed by helper class:

template<typename T>
class make_vector
{
    std
::vector<T> data;
public:
   
template<typename... A>
   
inline make_vector(A&&... a)
   
{
        data
.reserve(sizeof...(A));
       
auto f = {(data.emplace_back(std::forward<A>(a)), 0)...}; //hack to reduce code size
   
}
   
   
operator std::vector<T>() { return std::move(data); }
};

int main()
{
    std
::vector<std::unique_ptr<int>> v = make_vector<std::unique_ptr<int>>
   
{
        std
::unique_ptr<int>(new int{1}),
        std
::unique_ptr<int>(new int{2}),
        std
::unique_ptr<int>(new int{4}),
   
};
   
for(auto& i : v)
        std
::cout<< *i << "\n";
}


Ville Voutilainen

unread,
Aug 29, 2013, 1:56:31 AM8/29/13
to std-pr...@isocpp.org
So.. the problem at hand is that people claim that I can't move from an initializer_list of move-only type.
If I have a vector<unique_ptr<int>> member, I have to resort to wrapper types to have a ctor-initializer
initialize the member.
Hogwash, I say! Lambdas to the rescue!

struct X {
    vector<unique_ptr<int>> vup;
    X(unique_ptr<int> src)  // brace yourselves (hmm. no pun intended.), here we gooooo.....
        : vup{
                 [](unique_ptr<int> src){
                     vector<unique_ptr<int>> tmp;
                     tmp.push_back(move(src));
                     return tmp;
                 }(move(src)),
         }
    {
    }
};

int main()
{
    X x{unique_ptr<int>{new int{}}};
}
Reply all
Reply to author
Forward
0 new messages