当前位置: 首页 > 知识库问答 >
问题:

如何实现多重条件继承?

松雅昶
2023-03-14

我有一个类型<code>build</code>有一个标志模板,根据活动标志位,它从这些类型继承。这使我可以从具有大量配置的许多子类中“构建”类:

#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;
}

所以< code >构建

但它不编译,说<code>空</code>已经是一个直接基类:

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

我怎样才能做到这一点,而不必创建4个不同的空结构来避免冲突呢?

共有3个答案

赏阳嘉
2023-03-14

与此同时,我自己想了一个解决方案。理想情况下,我不想用空类型膨胀范围,也不想引入许多临时类型或继承链。所以我的想法是从ABCD的选择中创建一个std::tuple。这假设每个位都设置为一个类型,我的应用程序就是这种情况。

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>());
}

所以

decltype(flags_to_tuple<FLAG_BIT_A | FLAG_BIT_C, A, B, C, D>());

将导致<code>std::tuple

template<class A, template<class...> class B> struct rename_impl {};

template<
    template<class...> class A, class... T,
    template<class...> class B> struct rename_impl<A<T...>, B> {
    using type = B<T...>;
};

template<typename... types>
struct build_impl : types... {

};

template<std::uint8_t flags>
using flags_tuple_type = decltype(flags_to_tuple<flags, A, B, C, D>());

template<std::uint8_t flags>
using build = typename rename_impl<flags_tuple_type<flags>, build_impl>::type;

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

我不再需要任何<code>std::conditional

司徒经纶
2023-03-14

我正在编辑这个答案,因为似乎只有Clang编译这个代码,即使设置了C 20标志。我相信铿锵是正确的。查看答案的底部,用MRE快速调查一下。

因此,这个修改后的答案不再对C 20有要求,以换取更多的非通用类型:

将<code>空

template<typename T>
class empty { };

然后,您可以修改每个类型别名以使用<code>empty

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>>;

在C20中,在模板实例化中生成唯一类型的常见技巧是使用空lambda:[]{}

然后,您可以创建每个<code>flag_x_type</code>类型别名,如下所示:

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

这甚至保持了标准布局,这很好。

Clang成功编译我的老答案,MSVC和GCC失败都失败了。我相信Clang在这里是正确的,因为它也适用于所有“导致”两者都失败的情况,而不会改变代码的任何重要意义。

MSVC甚至在试图使用类型别名作为继承目标时中断(但只有当替代方法是空类而不是未赋值的lambda时),GCC在试图使用基类提供的成员函数时中断,即使当别名是“内联的”时它可以使用它。

最小可重现的故障示例:https://godbolt.org/z/7MEnn3bsf

沈琛
2023-03-14

下面是另一种使用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>);

编译器资源管理器

适用于clang、gcc和msvc,不会导致指数级实例化爆炸。

 类似资料:
  • 本文向大家介绍jQuery多条件筛选如何实现,包括了jQuery多条件筛选如何实现的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了jquery实现多条件筛选特效。分享给大家供大家参考。具体如下: 我们在电商平台购买商品时,在商品列表页根据品牌、款式、价格范围等条件进行筛选查询,当点击某个条件时,在页面上会显示用户所选择的条件集合,并且将对应的符合条件的商品信息展示出来。那么今天我们使用jQ

  • 主要内容:多继承下的构造函数,命名冲突在前面的例子中,派生类都只有一个基类,称为 单继承(Single Inheritance)。除此之外, C++也支持 多继承(Multiple Inheritance),即一个派生类可以有两个或多个基类。 多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、 C#、 PHP 等干脆取消了多继承。 多继承的语法也很简单,将多个基类用逗号隔开即可。例如已声明了类A

  • 条款36: 区分接口继承和实现继承 (公有)继承的概念看起来很简单,进一步分析,会发现它由两个可分的部分组成:函数接口的继承和函数实现的继承。这两种继承类型的区别和本书简介中所讨论的函数声明和函数定义间的区别是完全一致的。 作为类的设计者,有时希望派生类只继承成员函数的接口(声明);有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现;有时则希望同时继承接口和实现,并且不允许派生类改写任何

  • 继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。 回忆一下Animal类层次的设计,假设我们要实现以下4种动物: Dog - 狗狗; Bat - 蝙蝠; Parrot - 鹦鹉; Ostrich - 鸵鸟。 如果按照哺乳动物和鸟类归类,我们可以设计出这样的类的层次: 但是如果按照“能跑”和“能飞”来归类,我们就应该设计出这样的类的层次: 如果要把上面的两种分类都包含进来

  • 继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。 回忆一下Animal类层次的设计,假设我们要实现以下4种动物: Dog - 狗狗; Bat - 蝙蝠; Parrot - 鹦鹉; Ostrich - 鸵鸟。 如果按照哺乳动物和鸟类归类,我们可以设计出这样的类的层次: ┌───────────────┐ │

  • 我有根实体: 与父实体有关系的: 还有子实体: 我想搜索partner.category.code="ABC"的所有MoralEntity。