Constexpr ссылается на переменную анонимной структуры

Учитывая struct B, который наследует анонимный элемент данных union от struct A:

#include <cstddef>

struct A
{
    union
    {
        struct { int a, b, c; };
        int vals[3];
    };
};

struct B: A
{
    constexpr B(int x)
    :
        A{{{ x, x, x }}}
    {}

    constexpr int& operator[](size_t i)
    {
        return this->vals[i];
    }

    constexpr const int& operator[](size_t i) const
    {
        return this->vals[i];
    }
};

Объявляю переменную constexpr типа B, а затем вызываю ее operator[], чтобы присвоить возвращаемое значение a constexpr int:

int main()
{
    constexpr B b(7);
    constexpr int i = b[2];

    return 0;
}

Однако Clang 3.8 дает мне сообщение об ошибке

constexpr variable 'i' must be initialized by a constant expression

Проблема связана с анонимным union, поскольку, когда я просто использую int vals[3], все работает нормально.

Есть ли ограничение С++ 14 constexpr, которое мне не хватает?

Ответ 1

В дополнение к ответу на MM, в соответствии с инициализацией объединения С++, агрегированный инициализатор всегда инициализирует только первый член объединения, который становится активным член этого союза.

Итак, изменив A на int vals[3] первое объявление в union:

struct A
{
    union
    {
        int vals[3];
        struct { int a, b, c; };
    };
};

или определение конструктора, который инициализирует член int vals[3] вместо инициализации агрегата, который инициализирует первый член union:

struct A
{
    A(int a, int b, int c)
    : vals{ a, b c }
    {}

    union
    {
        struct { int a, b, c; };
        int vals[3];
    };
};

решает проблему чтения анонимного члена union int vals[3] в выражении constexpr.

Ответ 2

Это не разрешено:

constexpr int i = b[2];

b[2] не является константным выражением, поскольку содержит (ref: N4140 [expr.const]/2.8)

преобразование lvalue-to-rval (4.1) или модификация (5.17, 5.2.6, 5.3.2), которое применяется к значению gl, которое относится к неактивному члену объединения или его подобъекту;

Активным членом объединения является структура, потому что вы инициализировали этот член. Массив int неактивен.

Если вы изменили функцию operator[] на switch и вместо этого вернули член структуры, она должна работать.

Примечание: доступ к неактивному элементу вызывает поведение undefined. Хотя распространенные компиляторы поддерживают союзное сглаживание как расширение, это могло бы избежать некоторых проблем, если бы вы могли разработать свой код, чтобы не использовать слияние слияния.


Есть проблемы с анонимной структурой и ее инициализатором. В частности, [class.union]/5:

Объединение вида union { member-specification } ; называется анонимным объединением; он определяет неназванный объект неназванного типа. Спецификация участника анонимного объединения должна определять только нестатические элементы данных. [Примечание. Вложенные типы, анонимные союзы и функции не могут быть объявлены в анонимном объединении. -end note]

Таким образом, вы не можете иметь анонимную структуру внутри анонимного объединения. Вы должны сделать один из них не анонимным. Например:

struct A
{
    struct AU { int a, b, c; };
    union
    {
        AU a;
        int vals[3];
    };
};

который работает с инициализатором : A({x, x, x}). Непоследовательное поведение вокруг инициализатора A, которое вы видели, может быть ошибкой gcc.