Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr reference to a variable of an anonymous struct

Given struct B, which inherits an anonymous union data member from 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];
    }
};

I declare a constexpr variable of type B, then calling its operator[] to assign the return value to a constexpr int:

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

    return 0;
}

However Clang 3.8 gives me the error message

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

The issue is related to the anonymous union, since when I simply use int vals[3] then everything works fine.

Is there a C++14 constexpr restriction I'm missing?

like image 207
plasmacel Avatar asked Apr 04 '16 02:04

plasmacel


2 Answers

This is not allowed:

constexpr int i = b[2];

b[2] is not a constant expression because it contains (ref: N4140 [expr.const]/2.8)

an lvalue-to-rvalue conversion (4.1) or modification (5.17, 5.2.6, 5.3.2) that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;

The active member of the union is the struct because you initialized that member. The int array is inactive.

If you changed the operator[] function to switch and return a member of the struct instead, it should work.

Note: accessing the inactive member causes undefined behaviour. Although common compilers support union aliasing as an extension, it would avoid some trouble if you could design your code to not use union aliasing.


There are issues with the anonymous struct and its initializer too. Specifically, [class.union]/5:

A union of the form union { member-specification } ; is called an anonymous union; it defines an unnamed object of unnamed type. The member-specification of an anonymous union shall only define non-static data members. [Note: Nested types, anonymous unions, and functions cannot be declared within an anonymous union. —end note ]

So you cannot have the anonymous struct inside the anonymous union. You need to make one of them non-anonymous. For example:

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

which works with the initializer : A({x, x, x}). The inconsistent behaviour around the A initializer that you saw might be a gcc bug.

like image 160
M.M Avatar answered Oct 29 '22 21:10

M.M


In addition to M.M's answer, according to the C++ union initialization rules the aggregate initializer always initializes the first union member only, which becomes the active member of that union.

So changing A to be int vals[3] the first declaration in the union:

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

or defining a constructor which initializes the member int vals[3] instead of the aggregate initialization which initializes the first union member:

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

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

solves the problem of reading anonymous union member int vals[3] in a constexpr expression.

like image 25
plasmacel Avatar answered Oct 29 '22 20:10

plasmacel