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
.
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.
Pretty much redundant; constexpr variables have internal linkage per default.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With