Yo Yo
Yo Yo

Reputation: 21

multiple virtual inheritance and variadic template

In my project I played with the following design :

enum {
    A = 1, B = 2, C = 4
};
struct Foo { int foo; };
template <int> struct Bar;
template <> struct Bar<A> : public virtual Foo { int a; };
template <> struct Bar<B> : public virtual Foo { int b; };
template <> struct Bar<C> : public virtual Foo { int c; };

Now I can do something fun like :

template <> struct Bar<A|B> : public A, public B {};
template <> struct Bar<A|C> : public A, public C {};
template <> struct Bar<B|C> : public B, public C {};
template <> struct Bar<A|B|C> : public A, public B, public C {};

So that I can write :

Bar<A|C> bar;
bar.foo = 2;
bar.a = 1;
bar.c = 2;

Now I would like the generation of the combination classes Bar<X|Y|Z|..> to be automatically done when the user creates such an instance. Is this possible using some template magic ?

Something along the lines of :

template <int N, class ...Classes> struct Bar<N> : public Classes... {};
template <int N> struct Bar<N> : public Bar<N, generate_classes<N> > {};

where generate_classes would be able to generate the list of classes Bar<N> should inherit from.

Upvotes: 2

Views: 111

Answers (3)

n. m. could be an AI
n. m. could be an AI

Reputation: 120079

No need in fancy schmancy SFINAE, parameter packs or any such dark wizardry.

enum {
    A = 1, B = 2, C = 4
};

struct Foo { int foo; };

template <unsigned int> struct Bar; // unsigned because bit fiddling

template <> struct Bar<A> : public virtual Foo { int a; };
template <> struct Bar<B> : public virtual Foo { int b; };
template <> struct Bar<C> : public virtual Foo { int c; };

template <unsigned int i> struct Bar :
    public Bar<i & ~(i-1)>, // only least significant set bit 
    public Bar<i &  (i-1)>  // all other set bits
{ };

// checking
int main ()
{
  Bar<A|B|C> abc;

  abc.a = 0;  // ok
  abc.b = 0;  // ok
  abc.c = 0;  // ok

  Bar<A|B> ab;

  ab.a = 0;   // ok
  ab.b = 0;   // ok
  ab.c = 0;   // error

  Bar<A|C> ac;

  ac.a = 0;   // ok
  ac.b = 0;   // error
  ac.c = 0;   // ok

  Bar<9> x;   // error
}

Upvotes: 3

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275936

Some machinery to do compile-time unpacking of flags:

enum flag_e { None = 0, A = 1, B = 1<<1, C = 1<<2 };

template<flag_e...>
struct flags {using type=flags; constexpr flags(){}};
template<flag_e>
struct flag{using type=flag; constexpr flag(){}};

constexpr flags<A,B,C> all_flags{};

template<flag_e...lhs, flag_e...rhs>
constexpr flags<lhs...,rhs...> operator+(flags<lhs...>, flags<rhs...>)
{ return {}; }

template<flag_e lhs, flag_e...rhs>
inline constexpr flags<lhs, rhs...> operator+(flag<lhs>, flags<rhs...>)
{ return {}; }
template<flag_e...lhs, flag_e rhs>
inline constexpr flags<lhs..., rhs> operator+(flags<lhs...>, flag<rhs>)
{ return {}; }

template<flag_e...fs>
inline constexpr flags<fs...> operator+(flag<None>, flags<fs...>)
{ return {}; }
template<flag_e...fs>
inline constexpr flags<fs...> operator+(flags<fs...>, flag<None>)
{ return {}; }

template<flag_e f, flag_e...fs>
inline constexpr auto unpack( flag<f>, flags<fs...> x, flags<> )
-> flags<fs...>
{ return {}; }

template<flag_e f, flag_e...fs, flag_e c0, flag_e...checks>
inline constexpr auto unpack( flag<f> fin, flags<fs...> x, flags<c0, checks...> )
-> decltype( unpack( fin, x+flag<flag_e(f&c0)>{}, flags<checks...>{} ) )
{ return {}; }

template<flag_e f>
inline constexpr auto unpack( flag<f> fin )
-> decltype( unpack( flag<f>{}, flags<>{}, all_flags ) )
{ return {}; }

Then we use it:

template <int> struct Bar;

template <class flags> struct BarImpl;
template <flag_e...fs> struct BarImpl<flags<fs...>>:
  Bar<fs>...
{};
template <int flags> struct Bar:
  BarImpl<decltype(unpack(flag<flag_e(flags)>{}))>
{};
struct Foo { int foo; };
template <> struct Bar<A> : public virtual Foo { int a; };
template <> struct Bar<B> : public virtual Foo { int b; };
template <> struct Bar<C> : public virtual Foo { int c; };

Live example.

The code that lets you have bundles of flags and individual flags can be made more generic at the cost of mentioning the flag_e type more often.

I made it overly slick, with the ability to say flags<A>+flags<B> and get flags<A,B>, because I like that notation.

I then wrote unpack, which takes flag<A|B> and produces flags<A,B>.

In C++14 and 17 things get slicker, which folds and return type deduction and the like.

Upvotes: 0

Sopel
Sopel

Reputation: 1219

Instead of thinking about somehow generating a list of classes to be inherited you can think of it as choosing them. This one should be inherited, this one shouldn't. This can be achieved by static dispatching through template parameter. So we get a template that based on a bool parameter gives either T or an empty class. It is likely to be optimized (it may even be guaranteed in this case, but I'm not sure what exact requirements given by the standard are) by empty base optimization so there is no memory overhead.

#include <iostream>

enum {
    A = 1, B = 2, C = 4
};

template <class T, bool Enable>
struct or_empty;

template <class T>
struct or_empty<T, false>
{
    struct empty {};
    using type = empty;
};

template <class T>
struct or_empty<T, true>
{
    using type = T;
};

template <class T, bool Enable>
using or_empty_t = typename or_empty<T, Enable>::type;

struct Foo { int foo; };

template <int I> struct Bar : 
    public or_empty_t<Bar<A>, I&A>, 
    public or_empty_t<Bar<B>, I&B>, 
    public or_empty_t<Bar<C>, I&C> {};

template <> struct Bar<A> : public virtual Foo { int a; };
template <> struct Bar<B> : public virtual Foo { int b; };
template <> struct Bar<C> : public virtual Foo { int c; };


int main()
{
    Bar<A|C> bar;
    bar.foo = 2;
    bar.a = 1;
    // bar.b = 2; error
    bar.c = 2;

    std::cout << bar.foo << ' ' << bar.a << ' ' << bar.c << '\n';

    std::cout << sizeof(Bar<A>) << ' ' << sizeof(Bar<A|B>) << ' ' << sizeof(Bar<A|B|C>) << '\n';
}

Demo: http://coliru.stacked-crooked.com/a/f170fbd873739c38

Upvotes: 0

Related Questions