7

I'm compiling using g++ for C++ 17. I have the following:

std::array<std::vector<int>, 2> v = {{ {1,2}, {3,4} }};

I don't understand why if I remove the double braces for the array it does not work anymore.

std::array<std::vector<int>, 2> v = { {1,2}, {3,4} }; // Does not compile

I understand how std::array works and the need for the double braces in general, but as I'm compiling for C++17 I expected brace elision to come into play.

Why is brace elision not applicable here?

Svalorzen
  • 5,353
  • 3
  • 30
  • 54
  • 1
    What do you mean by "not working" - the code compiles with GCC 8.1.0 and `-std=c++17` –  Nov 14 '18 at 17:36
  • 1
    @NeilButterworth He meant about [this](https://wandbox.org/permlink/xI6iTYXN877wbQ7r). If I understood correctly. – JeJo Nov 14 '18 at 17:39
  • @JeJo Yes you are correct. – Svalorzen Nov 14 '18 at 17:40
  • @JeJo That code isn't compiled with `-std=c++17 ` –  Nov 14 '18 at 17:41
  • @NeilButterworth `g++ -std=c++17 main.cpp main.cpp: In function ‘int main()’: main.cpp:5:56: error: too many initializers for ‘std::array, 2>’ std::array, 2> v = { {1,2}, {3,4} };` – Svalorzen Nov 14 '18 at 17:43
  • Related: [Why can't a 2D std::array be initialized with two layers of list-initializers?](https://stackoverflow.com/questions/52231566/why-cant-a-2d-stdarray-be-initialized-with-two-layers-of-list-initializers). – xskxzr Nov 15 '18 at 06:58

2 Answers2

10

std::array<std::vector<int>, 2> is effectively

struct array {
    std::vector<int> elems[2];
};

elems is a subaggregate just fine. The issue is that per the language rules, if the initializer starts with a { it's always assumed that you aren't eliding braces; instead, {1, 2} is taken as the initializer of the entire subaggregate elems, attempting to initialize its first element with 1 and second element with 2 (this is obviously invalid - you can't convert an integer to a vector - but doesn't affect the interpretation), and {3, 4} is considered the initializer for the thing after elems - and since there are no such thing, it's another error.

Initializing the first element with something that's not a braced-init-list is sufficient to trigger brace elision:

std::array<std::vector<int>, 2> v = { std::vector<int>{1,2}, {3,4} }; 

Note that from a specification perspective, the library doesn't guarantee initialization of std::array<T, N> from anything other than another std::array<T, N> or a list of "up to N elements whose types are convertible to T". This notably excludes braced-init-lists because they have no type, and actually also disallows "double braces" because that's just a special case of having a single element that is a braced-init-list .

This is an area where we may have been better off specifying it with code. The core language rules defy easy specification in words and the implementation details will leak out - and have already done so.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Thank you for the clarification, it was very useful. I'm not sure I fully understood your second remark though. The initialization is not guaranteed in other cases means that it is unspecified behaviour? Why are also double braces disallowed (considering that the brace elision rule was made pretty much for `std::array` IIUC)? – Svalorzen Nov 14 '18 at 20:49
  • 2
    Formally, it's undefined behavior: the standard defines no behavior when a `std::array` is initialized with anything else. Double braces are not in the "defined to work" case because they are actually an initializer list containing a single element (the inner braced-init-list) with no type. Brace elision goes all the way back to C. `std::array` has nothing to do with it. The formal UB here is certainly a defect; the problem is finding the right words to define the behavior, preferably without regurgitating the several pages of aggregate initialization rules. – T.C. Nov 14 '18 at 23:52
4

As T.C. pointed out my original interpretation was not corret, brace elision is allowed see [dcl.init.aggr]p15:

Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the elements of a subaggregate; it is erroneous for there to be more initializer-clauses than elements. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the elements of the subaggregate; any remaining initializer-clauses are left to initialize the next element of the aggregate of which the current subaggregate is an element. ...

but std::array according to array.overview:

An array is an aggregate that can be list-initialized with up to N elements whose types are convertible to T.

which is not the case we have.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • But in my example the double brace is only required for `std::array`, which is an an aggregate, right? Or is brace elision only allowed when it's aggregate all the way down? – Svalorzen Nov 14 '18 at 17:55
  • I just noticed that `std::array, 2> v = {std::vector{1,2}, std::vector{3,4}};` compiles. Is this also the "does not begin with a left brace" case? – Svalorzen Nov 14 '18 at 18:01
  • 1
    @Svalorzen that is a different case of explicit initialization covered by [dcl.init.aggrp3.2](http://eel.is/c++draft/dcl.init.aggr#3.2) and [dcl.init.aggrp4.2)](http://eel.is/c++draft/dcl.init.aggr#4.2) – Shafik Yaghmour Nov 14 '18 at 18:25
  • OP's example is attempting to elide the braces for the `std::vector[2]` subaggregate. – T.C. Nov 14 '18 at 20:05
  • @T.C. so are you saying it should work and the compilers are non-conforming or that my choice of words is not correct or that it should not work but I am quoting the wrong parts? – Shafik Yaghmour Nov 14 '18 at 20:09
  • *If the initializer-list begins with a left brace,* Does this means *if the first initializer-clause of the initializer-list is a brace-init-list*? – Oliv Nov 14 '18 at 20:14
  • This is the right portion of the rules, but you are giving it the wrong interpretation. "subaggregate" doesn't require "aggregate all the way down", and [this example](https://stackoverflow.com/questions/53305831/brace-elision-in-stdarraystdvector/53308148#comment93493182_53306113) definitely relies on brace elision and is not related to what you quoted in response (`std::array` has exactly one element). – T.C. Nov 14 '18 at 20:22