Home > Net >  how can I achieve multiple conditional inheritance?
how can I achieve multiple conditional inheritance?

Time:09-01

I have a type build that has a flag template, and according to the active flag bits, it inherits from those types. this allows me to "build" classes from many subclasses with a great number of configurations:

#include <type_traits>
#include <cstdint>

struct A {  void a() {} };
struct B {  void b() {} };
struct C {  void c() {} };
struct D {  void d() {} };

constexpr std::uint8_t FLAG_BIT_A = 0b1 << 0;
constexpr std::uint8_t FLAG_BIT_B = 0b1 << 1;
constexpr std::uint8_t FLAG_BIT_C = 0b1 << 2;
constexpr std::uint8_t FLAG_BIT_D = 0b1 << 3;

struct empty {};

template<std::uint8_t flags> using flag_a_type = std::conditional_t<(flags& FLAG_BIT_A) == 1, A, empty>;
template<std::uint8_t flags> using flag_b_type = std::conditional_t<(flags& FLAG_BIT_B) == 1, B, empty>;
template<std::uint8_t flags> using flag_c_type = std::conditional_t<(flags& FLAG_BIT_C) == 1, C, empty>;
template<std::uint8_t flags> using flag_d_type = std::conditional_t<(flags& FLAG_BIT_D) == 1, C, empty>;

template<std::uint8_t flags>
struct build : flag_a_type<flags>, flag_b_type<flags>, flag_c_type<flags>, flag_d_type<flags> {

};

int main() {
    build<FLAG_BIT_A | FLAG_BIT_C> foo;
}

so build<FLAG_BIT_A | FLAG_BIT_C> should result in a class that inherits from A and from C.

but it doesn't compile, saying empty is already a direct base class:

error C2500: 'build<5>': 'empty' is already a direct base class

how can I achieve this without having to make 4 different empty structs to avoid the clash?

CodePudding user response:

Updated Answer

I'm editing this answer because it seems that only Clang compiles this code, even with the C 20 flag set. I'm not sure who is correct. See this godbolt: https://godbolt.org/z/9ePzzr78a

So, this revised answer no longer has a requirement for C 20, in exchange for just a bit more non-generic typing:

Change your empty class to:

template<typename T>
class empty { };

Then you can modify each of your type aliases to use empty<X>.

template<std::uint8_t flags> using flag_a_type = std::conditional_t<(flags & FLAG_BIT_A) != 0, A, empty<A>>;
template<std::uint8_t flags> using flag_b_type = std::conditional_t<(flags & FLAG_BIT_B) != 0, B, empty<B>>;
template<std::uint8_t flags> using flag_c_type = std::conditional_t<(flags & FLAG_BIT_C) != 0, C, empty<C>>;
template<std::uint8_t flags> using flag_d_type = std::conditional_t<(flags & FLAG_BIT_D) != 0, D, empty<D>>;

Old Answer Below

In C 20, a common trick to generate unique types in a template instantiation is to use an empty lambda: []{}

You can then create each of your flag_x_type type aliases as so:

template<std::uint8_t flags> using flag_a_type = std::conditional_t<(flags& FLAG_BIT_A) == 1, A, decltype([]{})>;

This even maintains standard layout, which is nice.

CodePudding user response:

This is a bit of a hacky solution as well (it's a bit harder to add new flags, the flag_x_type classes are not independent of each other and it's a bit more verbose), but your build would only inherit empty once, and you would not need four individual empty types:

#include <type_traits>
#include <cstdint>

struct A {  void a() {} };
struct B {  void b() {} };
struct C {  void c() {} };
struct D {  void d() {} };

constexpr std::uint8_t FLAG_BIT_A = 0b1 << 0;
constexpr std::uint8_t FLAG_BIT_B = 0b1 << 1;
constexpr std::uint8_t FLAG_BIT_C = 0b1 << 2;
constexpr std::uint8_t FLAG_BIT_D = 0b1 << 3;

struct empty {};

template<std::uint8_t flags> using flag_a_type = std::conditional_t<(flags& FLAG_BIT_A), A, empty>;

template<std::uint8_t flags> struct _flag_b_type : B, flag_a_type<flags> {};
template<std::uint8_t flags> struct flag_b_type : std::conditional_t<(flags& FLAG_BIT_B), _flag_b_type<flags>, flag_a_type<flags>>  {};

template<std::uint8_t flags> struct _flag_c_type : C, flag_b_type<flags> {};
template<std::uint8_t flags> struct flag_c_type : std::conditional_t<(flags& FLAG_BIT_C), _flag_c_type<flags>, flag_b_type<flags>> {};

template<std::uint8_t flags> struct _flag_d_type : D, flag_c_type<flags> {};
template<std::uint8_t flags> struct flag_d_type : std::conditional_t<(flags& FLAG_BIT_D), _flag_d_type<flags>, flag_c_type<flags>> {};

template<std::uint8_t flags>
struct build : flag_d_type<flags> {

};

int main() {
    build<FLAG_BIT_A | FLAG_BIT_C> foo;
    foo.a();
    //foo.b(); // error: 'struct build<5>' has no member named 'b'
    foo.c();
    //foo.d(); // error: 'struct build<5>' has no member named 'd'
}
  • Related