How should I brace-initialize an std::array of std::pairs?
Asked Answered
R

2

36
std::array<std::pair<int, int>, 2> ids = { { 0, 1 }, { 1, 2 } };

VS2013 error:

error C2440: 'initializing' : cannot convert from 'int' to 'std::pair' No constructor could take the source type, or constructor overload resolution was ambiguous`

What am I doing wrong?

Receptor answered 27/12, 2014 at 16:29 Comment(0)
J
39

Add another pair of braces.

std::array<std::pair<int, int>, 2> ids = { { { 0, 1 }, { 1, 2 } } };

std::array<T, N> is an aggregate class containing a member of type T[N]. Usually, you can initialise that the same way you would a plain T[N] array, but when you're dealing with a non-aggregate element type, you may need to be more explicit.

Jimjams answered 27/12, 2014 at 16:45 Comment(7)
This seems to explain everything, until you look at Joachim's answer using std::make_pair. How does that work, without the extra pair of enclosing braces?Grivet
@Grivet In general, braces can be omitted. int a[2][2] = { 0, 1, 2, 3 }; is perfectly valid. But when you're dealing with classes with user-provided constructors, things get a bit tricky: { 0, 1 } could be an attempt to initialise the first element, or the first sub-element. The ambiguity is resolved in favour of the first element. On the other hand, the result of make_pair can only be used to initialise the first sub-element.Jimjams
The only thing the standard guarantees to work is "array<T, N> a = { initializer-list }; where initializer-list is a comma-separated list of up to N elements whose types are convertible to T". The extra pair of braces will probably work on all current implementations, but isn't guaranteed.Prelature
@Prelature Not yet, anyway. There are several relevant open issues about arrays. One is that array<T, N> should be layout-compatible with T[N]. Another is that the member T elems[N]; in the standard's description of std::array, commented as "exposition only", isn't intended to work like how [objects.within.classes]p2 says such members should work. For practical purposes, this answer is correct. A strict reading of the standard doesn't support this answer, but does support the position that the standard is simply wrong and no meaningful conclusions can be drawn from it just yet. :)Jimjams
The suggestion here does not work if the array contains three pairs: std::array<std::pair<int, int>, 3> ids = { { { 0, 1 }, { 1, 2 }, { 2, 3 } } }; fails to compile. However, leaving away the = helps: std::array<std::pair<int, int>, 3> ids { { { 0, 1 }, { 1, 2 }, { 2, 3 } } }; compiles without an error. It took me quite a while to figure that out. Maybe the answer should be edited so that future visitors will be warned? Plus, bonus question from myself: Why is this so?Salvucci
@Salvucci Exactly what you say doesn't compile, does compile just fine. Double-checked on gcc.godbolt.org with current and older releases of GCC, clang, icc and MSVC.Jimjams
@hvd Oops, this is weird. Sorry to “wake you up” on this. I definitely observed the above, with a direct comparison of the two options, in Xcode. Now I cannot reproduce it. Looks like one of the cases when a restart of Xcode makes compilation errors go away? Strange it happened in this case, though.Salvucci
T
30

std::array is an aggregate. It has only one data member - an array of the specified type of the std::array specialization. According to the C++ Standard. (8.5.1 Aggregates)

2 When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order

So this record

std::array<std::pair<int, int>, 2> ids = { { 0, 1 }, { 1, 2 } };

has more initializers then there are data members in std::array.

The data member of std::array is in turn an aggregate. You have to provide for it an initializer list.

So the record will look like

std::array<std::pair<int, int>, 2> ids = { { { 0, 1 }, { 1, 2 } } };

For it would be more clear you can imagine the initialization the following way

std::array<std::pair<int, int>, 2> ids = { /* an initializer for data member of the array */ };

As the data member is aggregate then you have to write

std::array<std::pair<int, int>, 2> ids = { { /* initializers for the aggregate data member*/ } };

And at last

std::array<std::pair<int, int>, 2> ids = { { { 0, 1 }, { 1, 2 } } };
Thrice answered 27/12, 2014 at 17:2 Comment(5)
If you are going by the standard, then there's no guarantee that std::array has only one data member.Prelature
@Prelature Without this information you can not correctly initialize the array. At least for exposition the standard includes in the array definition the following data member T elems[N];Thrice
The standard guarantees that array<T, N> a = { initializer-list }; will work if the initializer-list has up to N elements each of a type convertible to T. There's no guarantee that the built-in array (if one is used - I do not know if it is possible to comply with all the requirements without using one) is the only data member.Prelature
@Prelature I also do not know what can substitute the array. It seems it would be better if there would be written explicitly that the internal representation is an array.Thrice
For the passing-by-reader, this stuff is now in 9.4.2Catenary

© 2022 - 2024 — McMap. All rights reserved.