Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static constexpr array of class objects inside the class itself

Is it possible to have something like this in C++:

struct Foo
{
    int x;
    constexpr Foo(int x) : x(x) {}

    static constexpr Foo table[] =
    {
        Foo(0),
        Foo(1),
        Foo(2),
    };
};

I have tried several combination, but none work. It works if table is not part of Foo class, however I would really like it to be part of the Foo namespace.


Edit:

The reason I want this is so I can access the table as Foo::table. I have several classes like this in a namespace and it is really convenient if I can import the class I am using by writing using someNamespace::Foo and then access the table as Foo::table. If the table is outside the class I have to always access it by writing someNamespace::fooTable.

like image 460
rozina Avatar asked Aug 29 '17 07:08

rozina


People also ask

Can constexpr be static?

constexpr int a = 2; Static specifies the lifetime of the variable. A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later.

Is static constexpr redundant?

Pretty much redundant; constexpr variables have internal linkage per default.

Where are constexpr variables stored?

Everyone knows the memory location tables of C and C++ programs. For operating systems, executable code and all the static variables get copied from the hard drive disc into the allocated areas of text, static ect. in the RAM.


2 Answers

You can use the following trick, which basically moves the table to a templated wrapper, which is instantiated only when the class definition of Foo is complete.

template<typename T>
struct Wrapper
{
    static constexpr T table[] = { T(0), T(1), T(2) };
};

struct Foo : public Wrapper<Foo>
{
    int x;
    constexpr Foo(int x) : x(x) {}
};

Not sure whether this is actually an acceptable workaround in your situation, but it is how you can get your example to compile and run.

If you want to specify the initialization values of your table entries within the Foo class, you can extend the wrapper to take those values:

template<typename T, int... Args>
struct Wrapper
{
    static constexpr T table[] = { T(Args)... };
};

struct Foo : public Wrapper<Foo, 0, 1, 2>
{
    int x;
    constexpr Foo(int x) : x(x) {}
};

In both cases you can have all your classes derive from Wrapper without the need to define additional ones, since the statics of Wrapper exist per instantiation. If you need your entries to take values other than int, you can also pass that type as another template argument:

template<typename T, typename A, A... Args>
struct Wrapper
{
    static constexpr T table[] = { T(Args)... };
};

struct Foo : public Wrapper<Foo, int, 0, 1, 2>
{
    int x;
    constexpr Foo(int x) : x(x) {}
};

struct Bar : public Wrapper<Bar, char, 'a', 'b', 'c'>
{
    char x;
    constexpr Bar(char x) : x(x) {}
};

Passing multiple arguments to each constructor is achievable as well. Use a std::pair or some other wrapper to group them in that case.

like image 157
nh_ Avatar answered Sep 17 '22 17:09

nh_


The compiler error is clear here:

error: invalid use of incomplete type 'struct Foo'
         Foo(0),
              ^
note: definition of 'struct Foo' is not complete until the closing brace
 struct Foo
        ^~~

Foo is considered an "incomplete type" until the closing brace of its definition is reached. The size of incomplete types is not known, so the compiler doesn't know how much space table would require.


Here's a workaround:

struct FooTable
{
    constexpr auto operator[](int n) const;
};  

struct Foo
{
    int x;
    constexpr Foo(int x) : x(x) {}

    constexpr static FooTable table{};
};

constexpr auto FooTable::operator[](int n) const
{
    constexpr Foo table[] =
    {
        Foo(0),
        Foo(1),
        Foo(2),
    };

    return table[n];
}

live example on wandbox

Usage:

int main()
{
    constexpr auto x = Foo::table[1];
}

If you don't want Foo to be copied, you can place table inside a "detail" namespace and then return const auto& from FooTable::operator[] - example here.

like image 25
Vittorio Romeo Avatar answered Sep 18 '22 17:09

Vittorio Romeo