Using std::array with initialization lists
Asked Answered
S

3

64

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?

Stigma answered 19/11, 2011 at 5:46 Comment(6)
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()?Lizabethlizard
@Chris: This works for me with gcc 4.6.1 on Mac OSX 10.6. What compiler options are you using?Mending
@juanchopanza: I'm using the TDM distribution of MinGW-w64, which has GCC 4.6.1 and I am using -std=c++0x.Stigma
@Chris. Interesting. I have the macports version of 4.6.1 and it works with the same compiler flag.Mending
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.Hyo
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.Hyo
F
109

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.

Flinty answered 19/11, 2011 at 6:9 Comment(5)
Did the standard committee do this on purpose?Woollen
Why doesn't the standard just require that std::array have a constructor that works with initializer lists?Goodall
@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.Flinty
@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.Flinty
Ok, so I found this on Wikipedia en.wikipedia.org/wiki/…. 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<std::string, 2> strings = { "a", "b" };Stigma
E
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.

Epigenesis answered 27/9, 2012 at 8:11 Comment(0)
H
5

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);
Husk answered 12/8, 2021 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.