简体   繁体   English

基于标志的多重继承

[英]Multiple inheritance based on flags

I have several classes, say A , B , and C , and corresponding flags HAS_A=1 , HAS_B=2 , and HAS_C=4 . 我有几个类,比如ABC ,相应的标志HAS_A=1HAS_B=2HAS_C=4 Is it possible to write a class in such a way that its parents (from A , B , and C ) will be determined by a combination of these flags? 是否有可能以这样的方式编写一个类,即它的父母(来自ABC )将由这些标志的组合决定?

Example: 例:

ParentsFromFlags<HAS_A | HAS_C> x;
// x ends up having the features of A and C

I know I can have variable multiple parents with <typename... Parents> , but I'd like this because I want to ensure that if A , B , and C are parents of the class, they will always appear in a certain order. 我知道我可以使用<typename... Parents>来变量多个父项,但我想这样,因为我想确保如果ABC是该类的父类,它们将始终以特定顺序出现。

This does the job, at the cost of introducing some extra class in the hierarcy... 这样做的工作是以在层级中引入一些额外的课程为代价的......

enum ParentFlags { HAS_A = 1, HAS_B = 2, HAS_C = 4 };

class A{};
class B{};
class C{};

template <int M>
class ParentClass {};

template <>
class ParentClass<0>{};

template <>
class ParentClass<HAS_A> : public A {};

template <>
class ParentClass<HAS_B> : public B{};

template <>
class ParentClass<HAS_C> : public C{};

template <int F, int M>
class ParentTraits : public ParentClass<F & M>, 
                     public ParentTraits<F & ~M, M << 1>
{};

template <int M>
class ParentTraits<0, M>
{};

template <int F>
class ParentFromFlags : public ParentTraits<F, 1>
{
};

int main()
{
    ParentFromFlags<HAS_A | HAS_B> ab;
    ParentFromFlags<HAS_A | HAS_C> ac;
    ParentFromFlags<HAS_A | HAS_B | HAS_C> abc;
    return 0;
}

This is a little more generic than what you're looking for, but all you'd need now is to substitute filtered_list for a meta function that filters the classes based on your flags. 这比你正在寻找的更通用,但你现在需要的是将filtered_list替换为根据你的标志过滤类的元函数。

template<typename... T>
struct type_list;

template<typename T>
struct filtered_list;

template<typename T, typename... U>
struct filtered_list<type_list<T,U...>> {
    using type = type_list<U...>;
};

template<typename TypeList>
using filtered_list_t = typename filtered_list<TypeList>::type;

template<typename T>
struct collect_base_classes;

template<typename... T>
struct collect_base_classes<type_list<T...>> : public T... {};

struct A { void test_a() {} };
struct B { void test_b() {} };
struct C { void test_c() {} };

class Test : public collect_base_classes<filtered_list_t<type_list<A,B,C>>> {};

int main() {
    Test t;
    t.test_a(); //error, we dropped A from our base class list
    t.test_b();
    t.test_c();
}

There is a simple way and an optimal way to solve this problem. 有一种简单的方法和解决这个问题的最佳方法。

The easiest solution is to just use a compile-time type selector like conditional_t , combined with an empty-base class: 最简单的解决方案是使用像conditional_t这样的编译时类型选择器,并结合空基类:

template <int M>
struct empty_base {};

template <int flags>
struct Foo 
    : std::conditional_t<flags & Has_A, A, empty_base<1>>
    , std::conditional_t<flags & Has_B, B, empty_base<2>>
    , std::conditional_t<flags & Has_C, C, empty_base<3>>
{
    int x;
};

The problem with this approach is that it won't be able to trigger the empty base class optimization in C++, due to the use of multiple inheritance. 这种方法的问题在于,由于使用了多重继承,它无法在C ++中触发空基类优化 As a result, the Foo values will be a single word larger than necessary. 结果,Foo值将是大于必要的单个单词。

You can solve this by chaining the bases in a way similar to what boost.compressed_pair does: 你可以通过类似于boost.compressed_pa​​ir的方式链接基数来解决这个问题:

template <class T1, class T2>
struct compressed_pair_of_bases: T1, T2 {};

template <class T1, int N>
struct compressed_pair_of_bases<T1, empty_base<N>>: T1 {};

template <bool Predicate, class T, class Next>
using predicated_parent_chain_t = typename std::conditional_t<Predicate,
    compressed_pair_of_bases<T, Next>,
    Next>;

template <int flags>
struct Bar :
    predicated_parent_chain_t<!!(flags & Has_A), A,
        predicated_parent_chain_t<!!(flags & Has_B), B,
            predicated_parent_chain_t<!!(flags & Has_C), C,
                empty_base<1>>>>
{
    int x;
};

This solution is able to completely optimize away the base types when they are not selected: 此解决方案能够在未选择基本类型时完全优化它们:

    std::cout << sizeof(Bar<0>); // prints 4 on a 32-bit target

Actually this is what I ended up with. 实际上这就是我最终的结果。 It's basically the same as marom's solution , but a bit more readable (at least for me) 它与marom的解决方案基本相同,但更具可读性(至少对我而言)

enum ParentFlags { HAS_A = 1, HAS_B = 2, HAS_C = 4 };

class A { int a; };
class B { int b[2]; };
class C { int c[3]; };

template <int Flags>
class ParentA {};

template <>
class ParentA<HAS_A> : public A
{};

template <int Flags, int ThisFlag = Flags & HAS_B>
class ParentB : public ParentA<Flags & ~HAS_B>
{};

template <int Flags>
class ParentB<Flags, HAS_B> : public ParentA<Flags & ~HAS_B>, public B
{};

template <int Flags, int ThisFlag = Flags & HAS_C>
class ParentC : public ParentB<Flags & ~HAS_C>
{};

template <int Flags>
class ParentC<Flags, HAS_C> : public ParentB<Flags & ~HAS_C>, public C
{
};

template <int Flags>
class ParentFromFlags : public ParentC<Flags>
{};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM