我有一个build具有标志模板的类型,并且根据活动标志位,它继承自这些类型。这使我能够从具有大量配置的许多子类“构建”类:
#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>
using flag_b_type = std::conditional_t<(flags & FLAG_BIT_B), B, empty>;
template<std::uint8_t flags>
using flag_c_type = std::conditional_t<(flags & FLAG_BIT_C), C, empty>;
template<std::uint8_t flags>
using flag_d_type = std::conditional_t<(flags & FLAG_BIT_D), D, 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;
}
Run Code Online (Sandbox Code Playgroud)
所以build<FLAG_BIT_A | FLAG_BIT_C>应该会产生一个继承自A和 的类C。
但它无法编译,说empty已经是直接基类:
error C2500: 'build<5>': 'empty' is already a direct base class
Run Code Online (Sandbox Code Playgroud)
我怎样才能实现这一目标,而不必制作 4 个不同的空结构来避免冲突?
Pat*_*rts 19
这是使用c++20 的另一种方法,它为基于位掩码的继承提供了更大的灵活性:
#include <concepts>
#include <cstdint>
template <std::integral auto, class...>
struct inherit_mask {};
template <auto flags, class Base, class... Bases>
requires((flags & 1) == 1)
struct inherit_mask<flags, Base, Bases...>
: Base, inherit_mask<(flags >> 1), Bases...> {};
template <auto flags, class Base, class... Bases>
struct inherit_mask<flags, Base, Bases...>
: inherit_mask<(flags >> 1), Bases...> {};
struct A { void a() {} };
struct B { void b() {} };
struct C { void c() {} };
struct D { void d() {} };
template <std::uint8_t flags>
using build = inherit_mask<flags, A, B, C, D>;
using foo = build<0b0101>;
static_assert(std::derived_from<foo, A>);
static_assert(not std::derived_from<foo, B>);
static_assert(std::derived_from<foo, C>);
static_assert(not std::derived_from<foo, D>);
Run Code Online (Sandbox Code Playgroud)
适用于 clang、gcc 和 msvc,并且不会导致指数实例化爆炸。
我正在编辑这个答案,因为即使设置了 C++20 标志,似乎也只有 Clang 编译此代码。我相信铿锵是正确的。请参阅答案底部,通过 MRE 对其进行快速调查。
因此,这个修改后的答案不再需要 C++20,以换取更多的非泛型类型:
将您的empty班级更改为:
template<typename T>
class empty { };
Run Code Online (Sandbox Code Playgroud)
然后您可以修改每个类型别名以使用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>>;
Run Code Online (Sandbox Code Playgroud)
在 C++20 中,在模板实例化中生成唯一类型的常见技巧是使用空 lambda:[]{}
然后,您可以创建每个flag_x_type类型别名,如下所示:
template<std::uint8_t flags> using flag_a_type = std::conditional_t<(flags& FLAG_BIT_A) == 1, A, decltype([]{})>;
Run Code Online (Sandbox Code Playgroud)
这甚至保持了标准布局,这很好。
Clang 成功编译了我的旧答案,MSVC 和 GCC 都失败了。我相信 Clang 在这里是正确的,因为它也适用于所有“导致”失败的情况,而不会改变代码的任何重要含义。
MSVC 甚至在尝试使用类型别名作为继承目标时也会中断(但前提是替代方案是空类而不是未计算的 lambda),GCC 在尝试使用基类提供的成员函数时会中断,即使它可以使用当别名是“内联”时。
失败的最小可重现示例:https://godbolt.org/z/7MEnn3bsf
与此同时,我自己也思考了一个解决方案。理想情况下,我不想用空类型来扩大范围或引入许多临时类型或继承链。所以我的想法是根据、和的std::tuple选择创建一个。这假设每个位都设置为一种类型,这就是我的应用程序的情况。ABCD
template<std::uint8_t flags, typename... types>
constexpr auto flags_to_tuple() {
using flag_tuple = std::tuple<types...>;
constexpr auto SET_BITS = std::popcount(flags); //counts the number of set bits
constexpr auto flags_to_array = [&]() {
std::array<unsigned, SET_BITS> result{};
unsigned ctr = 0u;
for (unsigned i = 0u; i < sizeof(flags) * 8; ++i) { //check each bit in flags
if (flags & (1 << i)) {
result[ctr] = i;
++ctr;
}
}
return result;
};
constexpr auto bit_array = flags_to_array();
auto make_tuple = [&]<typename I, I... indices>(std::index_sequence<indices...>) {
return std::tuple<std::tuple_element_t<bit_array[indices], flag_tuple>...>{};
};
return make_tuple(std::make_index_sequence<SET_BITS>());
}
Run Code Online (Sandbox Code Playgroud)
所以
decltype(flags_to_tuple<FLAG_BIT_A | FLAG_BIT_C, A, B, C, D>());
Run Code Online (Sandbox Code Playgroud)
将导致std::tuple<A, C>. 这样我就可以实现build仅从std::tuple类型继承的类:
decltype(flags_to_tuple<FLAG_BIT_A | FLAG_BIT_C, A, B, C, D>());
Run Code Online (Sandbox Code Playgroud)
我不再需要任何std::conditional带有此标志的空类型来进行元组到可变参数的转换。请参阅godbolt 上的完整代码