While (re)implementing a simple constexpr map, I wrote this (godbolt):
template <class key_type, class value_type, int N>
class flat_map
{
private:
struct pair
{
key_type key;
value_type value;
};
const pair elements[N];
public:
consteval flat_map(const pair (&arr)[N]) noexcept
: elements(arr) // works on gcc?!
{}
[[nodiscard]] consteval value_type operator[](const key_type key) const
{
for (const pair &elem : elements)
if (elem.key == key)
return elem.value;
throw "Key not found";
}
};
constexpr flat_map<int, char, 3> m = {{
{ 4, 'a' }, { -1, 'b' }, { 42, 'c' }
}};
static_assert(m[4] == 'a');
static_assert(m[-1] == 'b');
static_assert(m[42] == 'c');
int main()
{
return m[4]; // 97=='a'
}
I naively thought to set the private array elements
as const
and initialize it in the constructor; I was using gcc trunk as compiler, and all was seemingly working well.
When I decided to try it with msvc and clang, I had compilation errors: both were complaining about the array initialization requiring a brace-enclosed initializer list.
In hindsight the other compilers aren't particularly wrong, are they? Am I inadvertently using some gcc non standard extensions here?
Ehm, by the way, what would you do to avoid copying the array elements by hand?
The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of [dcl.init] for direct-initialization.
Otherwise, if the destination type is an array, the object is initialized as follows. Let x1, …, xk be the elements of the expression-list. If the destination type is an array of unknown bound, it is defined as having k elements. Let n denote the array size after this potential adjustment. If k is greater than n, the program is ill-formed. Otherwise, the ith array element is copy-initialized with xi for each 1 ≤ i ≤ k, and value-initialized for each k < i ≤ n. For each 1 ≤ i < j ≤ n, every value computation and side effect associated with the initialization of the ith element of the array is sequenced before those associated with the initialization of the jth element.
Point 16.5 takes precedence over all points that follow, in particular those that cover copy-initialization from a value of the same type (16.6 and 16.9). As such, an array data member can only be initialized by initializing each of the array’s elements individually. GCC’s behaviour is therefore non-conforming.