Reputation: 4232
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
.
Upvotes: 12
Views: 1258
Reputation: 93394
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];
}
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.
Upvotes: 8
Reputation: 2241
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.
Upvotes: 6