如何实现多重条件继承?

Sta*_*nny 25 c++ inheritance

我有一个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

这是使用另一种方法,它为基于位掩码的继承提供了更大的灵活性:

#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,并且不会导致指数实例化爆炸。


DXP*_*wer 9

更新答案

我正在编辑这个答案,因为即使设置了 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


Sta*_*nny 6

与此同时,我自己也思考了一个解决方案。理想情况下,我不想用空类型来扩大范围或引入许多临时类型或继承链。所以我的想法是根据、和的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 上的完整代码