Direct initialization of C-Array from a std::initializer_list

1,052 views
Skip to first unread message

Gonzalo BG

unread,
Mar 11, 2016, 8:26:08 AM3/11/16
to ISO C++ Standard - Discussion
The following program is not valid C++:

#include <initializer_list>

int main() {
  constexpr std::initializer_list<int> il{1, 2, 3};
  constexpr int a[3]{il};
  return 0;
}

Is there a way to _directly_ initialize a C-array from an initializer list? 

If not, does anybody know why and or which part of the standard prevents it (and how it could be changed to allow this)?

Clang even reports as error that "the array must be initialized from an initializer list". I kind of expected `std::initializer_list`
to work always work and be equivalent to an initializer list.

Nicol Bolas

unread,
Mar 11, 2016, 8:37:20 AM3/11/16
to ISO C++ Standard - Discussion

There's a difference between:

vector<int> a{1, 3, 4, 20};

and

array a[5]{1, 3, 4, 20};

The first converts the braced-init-list into an `std::initializer_list<int>` and passes it to `vector<int>`'s constructor. The second uses the values in the braced-init-list to initialize an aggregate. There is no `std::initializer_list` involved.

See, a {...} is not an `initializer_list`. It is a braced-init-list, which is not a C++ object of any kind. It is a construct used to initialize an object. It cannot be stored, passed around, or otherwise manipulated. It does one thing: initialize some object. And it does so immediately upon use. It has special, "magical" properties to perform this initialization.

Your creation of `il` uses a `braced-init-list` to create a `std::initializer_list`. After this process is done, the "magic" of the braced-init-list is gone. You don't have a special construct for initialization; all you have is a C++ type. And all uses of it follow the rules of C++ types.

And I highly doubt the standard will be changed to allow array initialization from an `initializer_list`. If you already have an `initializer_list`, you should just use it exactly as such.

Nicol Bolas

unread,
Mar 11, 2016, 8:39:20 AM3/11/16
to ISO C++ Standard - Discussion

Or to put it much more simply, Clang's error message needs to be changed.

Gonzalo BG

unread,
Mar 11, 2016, 8:41:55 AM3/11/16
to ISO C++ Standard - Discussion
I guess that now it makes sense why this cannot work. Just to double check that I understood correctly, the issue is that std::initializer_list does not have a conversion operator to a C-array and/or that C-arrays don't have a constructor from std::initializer_list?

Dilip Ranganathan

unread,
Mar 11, 2016, 8:49:56 AM3/11/16
to std-dis...@isocpp.org
Also.. for the line:


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

VS 2015 complains:

error C2127: 'il': illegal initialization of 'constexpr' entity with a non-constant expression

This has to be a bug, isn't it?


--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

Gonzalo BG

unread,
Mar 11, 2016, 9:04:13 AM3/11/16
to ISO C++ Standard - Discussion
The whole situation arises when trying to implement a small_vector that supports for example small_vector<const int> as type. 

Suppose I can use std::array<T, N> or T[N] for implementing the small vector storage (which is true for LiteralTypes). I can construct a std::array<const int, N> if I use a braced-init-list directly.

But if I have it as a data-member of some other type A, there is no way for any of A constructors to forward a braced-init-list without screwing up the distinction between {} and () constructors. Basically the variadic constructor won't behave as a constructor taking a std::initializer_list. 

Nicol Bolas

unread,
Mar 11, 2016, 9:31:27 AM3/11/16
to ISO C++ Standard - Discussion
On Friday, March 11, 2016 at 9:04:13 AM UTC-5, Gonzalo BG wrote:
The whole situation arises when trying to implement a small_vector that supports for example small_vector<const int> as type. 

Suppose I can use std::array<T, N> or T[N] for implementing the small vector storage (which is true for LiteralTypes). I can construct a std::array<const int, N> if I use a braced-init-list directly.

I don't know exactly what you mean by `small_vector`, but my general sense of such a concept is either:

1: A `vector` who's capacity has a compile-time maximum.

2: A `vector` who's capacity has a compile-time maximum, which represents the largest storage within the object itself. It can grow beyond that, but it will dynamically allocate memory to do so.

In neither case is `array<T, N>` appropriate. `vector`s grow by the user's request, whether it's within the small capacity or beyond it. That's what makes something a `vector`. So if you store an array of N `T` objects, then you have constructed those objects. And that's not acceptable, since the user hasn't necessarily asked to construct N `T` objects yet.

You would need an array of storage for them, a `array<aligned_storage<...>, N>`. But not an array of actual `T` objects. Not until the user explicitly allocates them.
 
But if I have it as a data-member of some other type A, there is no way for any of A constructors to forward a braced-init-list without screwing up the distinction between {} and () constructors.

Yes, the forwarding of braced-init-lists is not possible. But this isn't anything new.

Gonzalo BG

unread,
Mar 11, 2016, 1:09:06 PM3/11/16
to ISO C++ Standard - Discussion
All good points. FWIW I am dealing with Case 1: the capacity has a compile-time maximum. 

I am using `std::aligned_storage` except for `LiteralTypes`, for which I am using either a C-array or a std::array.

This allows using the whole vector API withing C++14 constexpr functions when the types are LiteralTypes, but comes at the cost of value initializing the elements at run-time. Since the vectors are small an on the stack, I hope that the compiler is smart enough to detect that I value initialization is zeroing the memory just to replace it with some other values afterwards, but I haven't measured.

For using aligned_storage one fundamentally needs 3 language features: placement new, reinterpret cast, and explicit destructor calls, none of which can be used within constexpr functions. I don't think these will be available soon, so for such a type, constexpr support comes with a run-time cost.

Johannes Schaub

unread,
Mar 11, 2016, 1:32:47 PM3/11/16
to std-dis...@isocpp.org
Why does clang error message need to be changed? It's totally accurate
afaics. Only an initializer list can be used to initialize the array.
Please note that you must differentiate (as you correctly described)
between an initializer_list and an initializer list. See here:
https://meta.stackexchange.com/questions/75798/unfortunate-auto-detection-of-synonyms-for-initializer-list

Gonzalo BG

unread,
Mar 11, 2016, 1:43:22 PM3/11/16
to ISO C++ Standard - Discussion
The first time I read it I interpreted it as "initializer_list is not an initializer_list". It was only funny because it was non-sensical.
A note that clarifies it a bit could have been helpful:

(note: std::initializer_list is a standard library type and not the language construct braced-initializer-list {} which is what you need to use here)

Nicol Bolas

unread,
Mar 11, 2016, 4:24:33 PM3/11/16
to ISO C++ Standard - Discussion

The standard term for the construct is "braced-init-list". So the Clang error should mirror what the standard calls it.

The best way to deal with overloaded terms is to stop overloading them. Call the list of subobjects at the top of a constructor a "member initializer list". Call the `{}` thing a "braced-init-list". And call the thing that's spelled `initializer_list` an "initializer list".

Nicol Bolas

unread,
Mar 11, 2016, 4:34:51 PM3/11/16
to ISO C++ Standard - Discussion
On Friday, March 11, 2016 at 1:09:06 PM UTC-5, Gonzalo BG wrote:
All good points. FWIW I am dealing with Case 1: the capacity has a compile-time maximum. 

I am using `std::aligned_storage` except for `LiteralTypes`, for which I am using either a C-array or a std::array.
 
This allows using the whole vector API withing C++14 constexpr functions when the types are LiteralTypes, but comes at the cost of value initializing the elements at run-time. Since the vectors are small an on the stack, I hope that the compiler is smart enough to detect that I value initialization is zeroing the memory just to replace it with some other values afterwards, but I haven't measured.

Assuming that we're talking about what C++ calls a "literal type", that guarantees nothing. Literal types do not have any requirements on the "cost of value initializing" them. Literal types do not have to be trivially copyable, trivially default constructible, or any other such thing. Their default constructor is not required to be `constexpr` (and even if it was, that's no guarantee it will be executed at compile time). So I see no reason why to make a distinction between literal and non-literal types for your class.

Now, if you were talking about trivial types, I could understand. But not literal types.
 
For using aligned_storage one fundamentally needs 3 language features: placement new, reinterpret cast, and explicit destructor calls, none of which can be used within constexpr functions. I don't think these will be available soon, so for such a type, constexpr support comes with a run-time cost.

So, how do you figure out which constructor of a literal type is the `constexpr` one? Because they only need one `constexpr` constructor; it doesn't have to be the default one.

Then again, I'm not sure why it matters if a class whose size is determined at runtime can be used in `constexpr` contexts. That doesn't seem to be a vital use case for them; you could just use a `std::array<T, N>` since `N` will be known at compile time anyway.

Johannes Schaub

unread,
Mar 11, 2016, 5:57:45 PM3/11/16
to std-dis...@isocpp.org

No that would be super confusing, please don't do that. Especially not the latter. You are mixing grammar production that parse the thing with the name of the thing. If I recall correctly, {...} is parsed by a production called braced-init-list. However the name for this construct is "initializer list":

"List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is called an initializer list".

You find similar examples in clause 8 with the grammar term "/initializer/" and the name "initializer". The distinction allows to talk aout the thing more abstractly. So a "= {...}" has an initializer list as initializer. But not a /braced-init-list/ as /initializer/.

Nicol Bolas

unread,
Mar 11, 2016, 8:52:56 PM3/11/16
to ISO C++ Standard - Discussion

The process is called "list-initialization". The grammatical construct `{...}` is called a "braced-init-list". The contents of a "braced-init-list" (the `...`) are the "initializer list".  The grammar makes this quite clear:

braced-init-list:
 
{ initializer-list , opt }
 
{ }

David Krauss

unread,
Mar 12, 2016, 2:16:50 AM3/12/16
to std-dis...@isocpp.org, Gonzalo BG
On 2016–03–11, at 9:26 PM, Gonzalo BG <gonza...@gmail.com> wrote:

The following program is not valid C++:

#include <initializer_list>

int main() {
  constexpr std::initializer_list<int> il{1, 2, 3};
  constexpr int a[3]{il};
  return 0;
}

Is there a way to _directly_ initialize a C-array from an initializer list? 

Supposing you’re actually interested in initializing a member, then you might try,

template< typename value_type, std::size_t n >
class non_aggregate_array
    : public std::array< value_type, n > {
    template< std::size_t ... i >
    non_aggregate_array( std::initializer_list< value_type > il, std::index_sequence< i ... > )
        : std::array< value_type, n > {{ il.begin()[ i ] ... }} {}
public:
    non_aggregate_array( std::initializer_list< value_type > il )
        : non_aggregate_array( il, std::make_index_sequence< n >{} ) {}
};

Columbo

unread,
Mar 12, 2016, 6:42:00 AM3/12/16
to ISO C++ Standard - Discussion, gonza...@gmail.com
We had this before, right?

Johannes Schaub

unread,
Mar 12, 2016, 7:37:08 AM3/12/16
to std-dis...@isocpp.org
No, as the grammar says the content is the "initializer-list" (note
the '-'). The whole thing, including the braces, is the "initializer
list" and grammatically is the "braced-init-list".

Johannes Schaub

unread,
Mar 12, 2016, 8:30:06 AM3/12/16
to std-dis...@isocpp.org
And even if so, then your recommendation was to call
std::initializer_list an "initializer list", which would still be
confusing given the different meaning of the latter in the Standard.

Tony V E

unread,
Mar 12, 2016, 9:46:37 AM3/12/16
to 'Johannes Schaub' via ISO C++ Standard - Discussion
So in the standard we have initializer_list, initializer-list, and initializer list. Three different things. How could anyone find that confusing?

Sent from my BlackBerry portable Babbage Device
  Original Message  
From: 'Johannes Schaub' via ISO C++ Standard - Discussion
Sent: Saturday, March 12, 2016 8:30 AM
To: std-dis...@isocpp.org
Reply To: std-dis...@isocpp.org
Subject: Re: [std-discussion] Re: Direct initialization of C-Array from a std::initializer_list

Nicol Bolas

unread,
Mar 12, 2016, 11:25:58 AM3/12/16
to ISO C++ Standard - Discussion
On Saturday, March 12, 2016 at 9:46:37 AM UTC-5, Tony VE wrote:
So in the standard we have initializer_list, initializer-list, and initializer list. Three different things. How could anyone find that confusing?

This is precisely why I prefer to name `{...}` by the grammatical name `braced-init-list`. You have a name that is clearly distinct from the type `std::initializer_list`, which is what 90% of people who are not familiar with the standard will think of when they see you write "initializer list".

Or to use the common phrase, if you know the difference between "initializer_list", "initializer-list", and "initializer list", you know too much about C++ ;)
Reply all
Reply to author
Forward
0 new messages