Выведите список типов на основе значения параметра не тип - PullRequest
4 голосов
/ 03 апреля 2020

У меня есть класс, который можно настроить с помощью неопределенного количества политик в аргументе шаблона c:

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

Указанные функции активируются с помощью битов constexpr uint8_t, таких как:

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

Как мне написать вспомогательные типы для вывода и пересылки правильного списка типов в class A variadi c аргумент шаблона?

Используя std::conditional, я мог написать что-то вроде этого, но это становится ужасным довольно быстро, как вы можете себе представить.

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

Или используя вспомогательные типы и специализацию шаблонов, которые я мог бы написать

template<uint8_t value>
struct Features {};

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

Но тогда мне нужно было бы распаковать Features<0x3>::type для экземпляра class A, и я не уверен, как мне это написать.

Ответы [ 2 ]

4 голосов
/ 03 апреля 2020

Чтобы превратить ваш флаг в набор (кортеж) объекта, вы можете использовать код, подобный следующему:

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

Но тогда мне нужно будет распаковать Features<0x3>::type для class A. экземпляр, и я не уверен, как мне это написать.

С дополнительным слоем вы можете "распаковать" кортеж:

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>

или

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>
1 голос
/ 03 апреля 2020

Слишком поздно играть?

Учитывая список X1 - X8 функций, я предлагаю добавить шаблон класса, который отображает число от 0 до 7 в классы X1 - X8 и наследовать от X1 - X8, только если первый логический параметр шаблона имеет вид 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 { };

Теперь структура Feature_helper, которая наследует право Feature для каждого бита в std::uint8_t число, просто

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>...
 { };

и A становятся

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

Ниже приведен полный пример компиляции

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

- - РЕДАКТИРОВАТЬ -

Наблюдать за OP

Обнаружил обратную сторону, наследование класса Feature_helper не будет прямым производным классом Feature. Это означает, что он не может наследовать конструкторы от X1, X2, ...

Не уверен, что понял ваше требование, но я полагаю, вы можете решить проблему (если между * 1040 нет коллизии * construnctors) с использованием большого числа using.

В следующем примере я добавил конструктор int для X1 и конструктор long для X2.

Обратите внимание на X1() = default; и X2() = default;: при добавлении явного конструктора конструкторы / деструкторы по умолчанию удаляются; поэтому вы должны заново установить их по умолчанию (возможно, также скопировать / переместить конструкторы).

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