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'
}