61

Unless I am mistaken, it should be possible to create a std:array in these ways:

std::array<std::string, 2> strings = { "a", "b" };
std::array<std::string, 2> strings({ "a", "b" });

And yet, using GCC 4.6.1 I am unable to get any of these to work. The compiler simply says:

expected primary-expression before ',' token

and yet initialization lists work just fine with std::vector. So which is it? Am I mistaken to think std::array should accept initialization lists, or has the GNU Standard C++ Library team goofed?

Chris_F
  • 4,991
  • 5
  • 33
  • 63
  • I'm not sure whether or not this should work (I'm not up to date on 0x stuff) but, bug or not, I think it's because you're using `std::string` with string literals. Have you tried wrapping the string literals with `std::string()`? – John Chadwick Nov 19 '11 at 06:09
  • @Chris: This works for me with gcc 4.6.1 on Mac OSX 10.6. What compiler options are you using? – juanchopanza Nov 19 '11 at 09:11
  • @juanchopanza: I'm using the TDM distribution of MinGW-w64, which has GCC 4.6.1 and I am using -std=c++0x. – Chris_F Nov 19 '11 at 09:18
  • @Chris. Interesting. I have the macports version of 4.6.1 and it works with the same compiler flag. – juanchopanza Nov 19 '11 at 09:25
  • Both work for me on gcc-4.6.1 linux as does the version with two sets of braces which is really most "correct". All three ICE on gcc-4.7. Filing a bug report. – emsr Nov 20 '11 at 15:58
  • I was wrong on the second form. It doesn't compile of gcc-4.6.1 Linux. This behavior is correct as noted below. The second one can't work as noted below by @NicolBolas. The second constructor form gives a sensible error on both versions. Got mixed up. Sorry. – emsr Nov 20 '11 at 16:21

3 Answers3

107

std::array is funny. It is defined basically like this:

template<typename T, int size>
struct std::array
{
  T a[size];
};

It is a struct which contains an array. It does not have a constructor that takes an initializer list. But std::array is an aggregate by the rules of C++11, and therefore it can be created by aggregate initialization. To aggregate initialize the array inside the struct, you need a second set of curly braces:

std::array<std::string, 2> strings = {{ "a", "b" }};

Note that the standard does suggest that the extra braces can be elided in this case. So it likely is a GCC bug.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Did the standard committee do this on purpose? – Daniel Nov 19 '11 at 06:16
  • Why doesn't the standard just require that `std::array` have a constructor that works with initializer lists? – Paul Manta Nov 19 '11 at 06:17
  • 6
    @PaulManta: Because then it wouldn't qualify for aggregate initialization. Aggregate initialization can be folded in at compile-time, depending on the type of the array elements (std::string doesn't qualify). Initializer list initialization *must* be a runtime function call, regardless of the type of the array elements. – Nicol Bolas Nov 19 '11 at 06:25
  • 2
    @Dani: Not necessarily "on purpose". It's simply a necessary outgrowth of how things have to work. `std::array` is intended to be a compile-time type, so it needs to work with aggregate initialization. – Nicol Bolas Nov 19 '11 at 06:28
  • 4
    Ok, so I found this on Wikipedia http://en.wikipedia.org/wiki/Array_%28C%2B%2B%29#Implementation_as_aggregate. Note how it says *Note that for standard conforming compilers it is possible to use fewer braces (according to 8.5.1 (11) of the Standard)*. So it seems the GNU team has goofed a bit and **SHOULD** be able to initialize a std::array as std::array strings = { "a", "b" }; – Chris_F Nov 19 '11 at 06:28
13

To add to the accepted answer:

std::array<char, 2> a1{'a', 'b'};
std::array<char, 2> a2 = {'a', 'b'};
std::array<char, 2> a3{{'a', 'b'}};
std::array<char, 2> a4 = {{'a', 'b'}};

all work on GCC 4.6.3 (Xubuntu 12.01). However,

void f(std::array<char, 2> a)
{
}

//f({'a', 'b'}); //doesn't compile
f({{'a', 'b'}});

the above requires double braces to compile. The version with single braces results in the following error:

../src/main.cc: In function ‘int main(int, char**)’:
../src/main.cc:23:17: error: could not convert ‘{'a', 'b'}’ from ‘<brace-enclosed initializer list>’ to ‘std::array<char, 2ul>’

I'm not sure what aspect of type inference/conversion makes things work this way, or if this is a quirk of GCC's implementation.

Joseph
  • 539
  • 5
  • 13
3

A bit late to the game but this is how I do it in C++17. Not using an initializer list just a variadic list of values. like this : auto ar2 = create_array(1, 2, 3, 4);

#include <array>
#include <type_traits>

namespace traits
{

template<typename T, typename... Ts>
struct array_type
{
  using type = T;
};

template<typename T, typename... Ts>
static constexpr bool are_same_type()
{
  return std::conjunction_v<std::is_same<T, Ts>...>;
}

}

template<typename... T>
constexpr auto create_array(const T&&... values)
{
  using array_type = typename traits::array_type<T...>::type;
  static_assert(sizeof...(T) > 0, "an array must have at least one element");
  static_assert(traits::are_same_type<T...>(), "all elements must have same type");
  return std::array<array_type, sizeof...(T)>{ values... };
}

template<typename T, typename... Ts>
constexpr auto create_array_t(const Ts&&... values)
{
  using array_type = T;
  static_assert(sizeof...(Ts) > 0, "an array must have at least one element");
  static_assert(traits::are_same_type<Ts...>(), "all elements must have same type");
  return std::array<array_type, sizeof...(Ts)>{ static_cast<T>(values)... };
}

// to create a std::array of specific type
auto ar = create_array_t<std::uint8_t>(1u, 2u, 3u, 4u);
static_assert(ar.size() == 4);

// to create an array and let the compiler deduce its type
auto ar2 = create_array(1, 2, 3, 4);
static_assert(ar2.size() == 4);