Pass nested types to a parent class without repeating yourself (DRY) in C++20

Viewed 180

As shown in the example below, I need my Component struct to be able to access all the "fields" structs defined in a child struct. But I have some constraints:

  • The fields must be declared as nested types of the inheriting struct.
  • I cannot use any library like BOOST to solve that problem
  • There can be a theoretically infinite number of fields
  • Each field needs to be declared as a standalone struct so it can be referenced later and avoid being mixed up with another boolean field for example

The template parameters of the Component and Field structs can be modified however you like, as long as the Field has its TType parameter. Important note, I am using C++20.

// * CRTP stands for Curiously recurring template pattern
template <typename TCrtp>
struct Component
{
    template <typename TType>
    struct Field
    {
        using Type      = TType;
        using Component = TCrtp;
    };

    using ComponentType = TCrtp;

    // Because TCrtp is the inheriting class, the 'TCrtp::Fields' aka 'TestComponent::Fields' type
    // can be accessed from here to do anything I need to
};

struct TestComponent : Component<TestComponent>
{
    struct Field1: Field<bool>  {};
    struct Field2: Field<float> {};

    // Problem: Can we find a way to fill this tuple automatically
    // either from this class or the parent one without using header tool
    // or even macros if this is possible

    // The goal here is to avoid the programer that is creating this class to repeat itself
    // by having to fill manually this tuple and thus potentially forgetting a field, that would cause him
    // some troubles (bugs) later on...
    using Fields = std::tuple<Field1, Field2>;
};

Unfortunately, C++ doesn't allow type declarations inside of template parameters.
I also already tried to use and modify this answer to generate my code, but the problem here is that the macro would need to account for 2 parameters instead of one (one for the name of the field, and one for the type), making it quite tricky since a fair bit of logic is needed in order to achieve what I need.

1 Answers

With MACRO, up to hard coded limit, you might do something like:

#define COUNT_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...)    N
#define COUNT(...)   COUNT_N(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

#define CONCAT(A, B) A ## B

#define FIELDS_VAR(N) CONCAT(FIELDS_, N)
#define USING_VAR(N) CONCAT(USING_, N)

#define TAKE_FIRST(_1, ...) _1

#define FIELD(name, type) struct name : Field<type>{};

#define FIELDS_1(_1) FIELD _1
#define FIELDS_2(_1, _2) FIELD _1 FIELD _2
#define FIELDS_3(_1, _2, _3) FIELD _1 FIELD _2 FIELD _3

#define USING_1(_1) using Fields = std::tuple<TAKE_FIRST _1>
#define USING_2(_1, _2) using Fields = std::tuple<TAKE_FIRST _1, TAKE_FIRST _2>
#define USING_3(_1, _2, _3) using Fields = std::tuple<TAKE_FIRST _1, TAKE_FIRST _2, TAKE_FIRST _3>

#define FIELDS(...) FIELDS_VAR(COUNT(__VA_ARGS__)) (__VA_ARGS__) \
                    USING_VAR(COUNT(__VA_ARGS__)) (__VA_ARGS__) 

struct TestComponent : Component<TestComponent>
{
    FIELDS((Field1, bool), (Field2, float));
};

Demo

Related