Deduce a list of types based on non-type parameter value

Viewed 98

I have a class that can be customized with an undefined number of policies in a variadic template argument:

template<typename... Features>
class A : public Features... {};

Said features are enabled through bits of a constexpr uint8_t, such as:

0x1 --> X1 enabled
0x2 --> X2 enabled
0x3 --> X1, X2 enabled
0x4 --> X3 enabled
0x5 --> X1, X3 enabled
etc...

How can I write helper types to deduce and forward the correct list of types to class A's variadic template argument?

Using std::conditional I could write something like this, but it gets horrendous quite rapidly as you can imagine.

using ExtendedA = A<MandatoryFeature, std::conditional_t<HasX1(), X1, std::monostate>;

Or uisng helper types and template specialization I could write

template<uint8_t value>
struct Features {};

template<>
struct Features<0x3> {
    using type = std::tuple<X1, X2>;
};

But then I would need to unpack Features<0x3>::type for class A's instance, and I'm not sure how I should write that.

2 Answers

To transform your flag into collection (tuple) of feature, you might use code similar to:

template <std::size_t> struct Feature;

template <> struct Feature<0> { using type = X0; };
template <> struct Feature<1> { using type = X1; };
template <> struct Feature<2> { using type = X2; };
template <> struct Feature<3> { using type = X3; };
template <> struct Feature<4> { using type = X4; };
template <> struct Feature<5> { using type = X5; };
// ...
constexpr std::size_t FeaturesCount = /*..*/;

template <std::size_t Flag, std::size_t...Is>
auto CollectFeaturesImpl(std::index_sequence<Is...>)
-> decltype(std::tuple_cat(
       std::conditional_t<
           (Flag & (1U << Is)) != 0,
           std::tuple<typename Feature<Is>::type>,
           std::tuple<>>
       {}...
   ))

template <std::size_t Flag>
using CollectFeatures_t =
    decltype(CollectFeaturesImpl<Flag>(std::make_index_sequence<FeaturesCount>()));

But then I would need to unpack Features<0x3>::type for class A's instance, and I'm not sure how I should write that.

With extra layer, you might "unpack" tuple:

template<typename Tuple> struct AFromTuple;

template<typename... Features>
struct AFromTuple<std::tuple<Features...>>
{
    using type = A<Features...>;
};

using myA = AFromTuple<std::tuple<X1, X3>>::type; // A<X1, X3>

or

template<typename Tuple> struct FromTuple;

template<typename... Features>
struct FromTuple<std::tuple<Features...>>
{
    template <template <typename...> Class C>
    using map = C<Features...>;
};

using myA = FromTuple<std::tuple<X1, X3>>::map<A>; // A<X1, X3>

Is too late to play?

Given a list X1-X8 of features, I propose to add a template class that maps number from 0 to 7 to classes X1-X8 and inherit from X1-X8 only if a first boolean template parameter is true

template <bool, std::size_t>
struct Feature
 { };

template <> struct Feature<true, 0> : public X1 { };
template <> struct Feature<true, 1> : public X2 { };
template <> struct Feature<true, 2> : public X3 { };
template <> struct Feature<true, 3> : public X4 { };
template <> struct Feature<true, 4> : public X5 { };
template <> struct Feature<true, 5> : public X6 { };
template <> struct Feature<true, 6> : public X7 { };
template <> struct Feature<true, 7> : public X8 { };

Now a Feature_helper struct, that inherit the right Feature for every bit in a std::uint8_t number, is simple

template <std::uint8_t, typename = std::make_index_sequence<8u>>
struct Feature_helper;

template <std::uint8_t u8, std::size_t ... Is>
struct Feature_helper<u8, std::index_sequence<Is...>>
   : public Feature<(u8 & (1 << Is)) != 0u, Is>...
 { };

and A become

template <std::uint8_t u8>
class A : public Feature_helper<u8>
 { };

The following is a full compiling example

#include <cstdint>
#include <utility>
#include <type_traits>

struct X1 { };
struct X2 { };
struct X3 { };
struct X4 { };
struct X5 { };
struct X6 { };
struct X7 { };
struct X8 { };

template <bool, std::size_t>
struct Feature
 { };

template <> struct Feature<true, 0> : public X1 { };
template <> struct Feature<true, 1> : public X2 { };
template <> struct Feature<true, 2> : public X3 { };
template <> struct Feature<true, 3> : public X4 { };
template <> struct Feature<true, 4> : public X5 { };
template <> struct Feature<true, 5> : public X6 { };
template <> struct Feature<true, 6> : public X7 { };
template <> struct Feature<true, 7> : public X8 { };

template <std::uint8_t, typename = std::make_index_sequence<8u>>
struct Feature_helper;

template <std::uint8_t u8, std::size_t ... Is>
struct Feature_helper<u8, std::index_sequence<Is...>>
   : public Feature<(u8 & (1 << Is)) != 0u, Is>...
 { };

template <std::uint8_t u8>
class A : public Feature_helper<u8>
 { };

int main()
 {
   static_assert( true  == std::is_base_of_v<X1, A<5u>> );
   static_assert( false == std::is_base_of_v<X2, A<5u>> );
   static_assert( true  == std::is_base_of_v<X3, A<5u>> );
   static_assert( false == std::is_base_of_v<X4, A<5u>> );
   static_assert( false == std::is_base_of_v<X5, A<5u>> );
   static_assert( false == std::is_base_of_v<X6, A<5u>> );
   static_assert( false == std::is_base_of_v<X7, A<5u>> );
   static_assert( false == std::is_base_of_v<X8, A<5u>> );
 }

-- EDIT --

The OP observe

Found a downside, class inheriting Feature_helper would not be a direct derived class of a Feature. Meaning it cannot inherit constructors from X1, X2, ...

Not sure to understand your requirement but I suppose you can solve the problem (if there isn't collision between Feature construnctors) using a lot of using.

I've added, in the following example, a int contructor for X1 and a long constructor for X2.

Observe the X1() = default; and X2() = default;: adding an explicit constructor the default constructors/destructors are deleted; so you have to re-default them explicitly (maybe also copy/move constructors).

#include <cstdint>
#include <utility>
#include <iostream>
#include <type_traits>

struct X1 { X1(int) { std::cout << "X1 constructor" << std::endl; }
            X1() = default; };
struct X2 { X2(long) { std::cout << "X2 constructor" << std::endl; }
            X2() = default; };
struct X3 { };
struct X4 { };
struct X5 { };
struct X6 { };
struct X7 { };
struct X8 { };

template <bool, std::size_t>
struct Feature
 { };

template <> struct Feature<true, 0> : public X1 { using X1::X1; };
template <> struct Feature<true, 1> : public X2 { using X2::X2; };
template <> struct Feature<true, 2> : public X3 { };
template <> struct Feature<true, 3> : public X4 { };
template <> struct Feature<true, 4> : public X5 { };
template <> struct Feature<true, 5> : public X6 { };
template <> struct Feature<true, 6> : public X7 { };
template <> struct Feature<true, 7> : public X8 { };

template <std::uint8_t, typename = std::make_index_sequence<8u>>
struct Feature_helper;

template <std::uint8_t u8, std::size_t ... Is>
struct Feature_helper<u8, std::index_sequence<Is...>>
   : public Feature<(u8 & (1 << Is)) != 0u, Is>...
 { using Feature<(u8 & (1 << Is)) != 0u, Is>::Feature...; };

template <std::uint8_t u8>
class A : public Feature_helper<u8>
 { using Feature_helper<u8>::Feature_helper; };

int main()
 {
   static_assert( true  == std::is_base_of_v<X1, A<5u>> );
   static_assert( false == std::is_base_of_v<X2, A<5u>> );
   static_assert( true  == std::is_base_of_v<X3, A<5u>> );
   static_assert( false == std::is_base_of_v<X4, A<5u>> );
   static_assert( false == std::is_base_of_v<X5, A<5u>> );
   static_assert( false == std::is_base_of_v<X6, A<5u>> );
   static_assert( false == std::is_base_of_v<X7, A<5u>> );
   static_assert( false == std::is_base_of_v<X8, A<5u>> );

   A<3u>  a1(1);  // call the X1(int) constructor
   A<3u>  a2(2l); // call the X2(long) constructor
 }
Related