Is gcc wrong to allow the initialization of a const array member with another array reference?
Asked Answered
C

2

7

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?

Ciri answered 12/2, 2023 at 18:9 Comment(3)
You can't copy the elements by hand, once a const array is created you can't change it.Corotto
I think gcc got it wrong. No way you can initialize an array like this.Milkwort
@MarkRansom Well you're right, const must be removed for that.Ciri
A
8

[class.base.init]/7:

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.

[dcl.init.general]/16.5:

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 ≤ ik, and value-initialized for each k < in. For each 1 ≤ i < jn, 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.

Arabist answered 12/2, 2023 at 18:45 Comment(0)
S
7

Am I inadvertently using some gcc non standard extensions here?

Yes... I suppose gcc is using a non standard extension

Ehm, by the way, what would you do to avoid copying the array elements by hand?

Why avoid it? Using a delegating constructor is simple.

You can substitute your constructor with the followings

template <std::size_t ... Is>
consteval flat_map(const pair (&arr)[N],
                   std::index_sequence<Is...> const &) noexcept
  : elements{ arr[Is]... }
   {}

consteval flat_map(const pair (&arr)[N]) noexcept
  : flat_map(arr, std::make_index_sequence<N>{})
   {}
Salomon answered 12/2, 2023 at 18:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.