Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ How to make an std::tuple type based on partial specializations?

I have a template with an integer parameter, but the base template is disabled by static_assert() like this. (I only want some certain specialization forms; I want any argument passed to the template to be prohibited except for certain arguments)

template<ItemID item_id> struct ItemTemplate{
    static_assert(item_id == -1,"Cann't use unspecialized ItemTemplate!");
    static ItemID id{ std::numeric_limits<ItemID>::max() };
    //...
};

I also have several specializations form for this template like (I often add or delete some of them)

template<> struct ItemTemplate<1>{
    static constexpr ItemID id{1};
    //..
};
template<> struct ItemTemplate<2>{
    static constexpr ItemID id{2};
    //...
};

Now I want to create a std::tuple which is initialised only by all available types. So in the above example, ItemTemplate<1> and ItemTemplate<2>, but not ItemTemplate<3> and other non-specialized types. How do I achieve this?

like image 315
炸鱼薯条德里克 Avatar asked Oct 30 '22 02:10

炸鱼薯条德里克


1 Answers

I see a way but only if you forget the static_assert() denial way and define only the specializations of ItemTemplate.

The following is a simplified example where I define only some specializations of foo and the foo generic struct remain undefined.

template <std::size_t>
struct foo;

template <> struct foo<2U> {};
template <> struct foo<3U> {};
template <> struct foo<5U> {};
template <> struct foo<7U> {};

Now you need something to detect if a type is defined or not; by example, the following

template <typename T, std::size_t = sizeof(T)>
std::true_type existH (int);

template <typename>
std::false_type existH (long);

template <typename T>
using exist = decltype(existH<T>(0));

That is: from exist<foo<0>>::value you get false and from exist<foo<2>>::value you get true.

Now you need a list (usable compile time) of indexes of foo specialization defined from a lower limit (zero, by example) to an upper limit.

You can obtain it with

template <std::size_t I, std::size_t topI, typename,
          bool = (I == topI) || exist<foo<I>>::value>
struct fooIndexList;

template <std::size_t topI, std::size_t ... Ixs>
struct fooIndexList<topI, topI, std::index_sequence<Ixs...>, true>
 { using type = std::index_sequence<Ixs...>; };

template <std::size_t I, std::size_t topI, std::size_t ... Ixs>
struct fooIndexList<I, topI, std::index_sequence<Ixs...>, true>
 { using type = typename fooIndexList<I+1U, topI,
                   std::index_sequence<Ixs..., I>>::type; };

template <std::size_t I, std::size_t topI, std::size_t ... Ixs>
struct fooIndexList<I, topI, std::index_sequence<Ixs...>, false>
 { using type = typename fooIndexList<I+1U, topI,
                   std::index_sequence<Ixs...>>::type; };

Using fooIndexList, obtaining a std::tuple with all the foo defined (from zero to an upper limit) is very simple:

template <std::size_t ... Idx>
constexpr auto makeFooTupleH (std::index_sequence<Idx...> const &)
 { return std::make_tuple( foo<Idx>{} ... ); }

constexpr auto makeFooTuple ()
 { return makeFooTupleH(
      typename fooIndexList<0U, 100U, std::index_sequence<>>::type {}); }

In the example the upper limit is 100 but can be a template parameter of makeFooTuple().

The following is a full compiling example

#include <tuple>
#include <utility>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t = sizeof(T)>
std::true_type existH (int);

template <typename>
std::false_type existH (long);

template <typename T>
using exist = decltype(existH<T>(0));

template <std::size_t>
struct foo;

template <> struct foo<2U> {};
template <> struct foo<3U> {};
template <> struct foo<5U> {};
template <> struct foo<7U> {};

template <std::size_t I, std::size_t topI, typename,
          bool = (I == topI) || exist<foo<I>>::value>
struct fooIndexList;

template <std::size_t topI, std::size_t ... Ixs>
struct fooIndexList<topI, topI, std::index_sequence<Ixs...>, true>
 { using type = std::index_sequence<Ixs...>; };

template <std::size_t I, std::size_t topI, std::size_t ... Ixs>
struct fooIndexList<I, topI, std::index_sequence<Ixs...>, true>
 { using type = typename fooIndexList<I+1U, topI,
                   std::index_sequence<Ixs..., I>>::type; };

template <std::size_t I, std::size_t topI, std::size_t ... Ixs>
struct fooIndexList<I, topI, std::index_sequence<Ixs...>, false>
 { using type = typename fooIndexList<I+1U, topI,
                   std::index_sequence<Ixs...>>::type; };


template <std::size_t ... Idx>
constexpr auto makeFooTupleH (std::index_sequence<Idx...> const &)
 { return std::make_tuple( foo<Idx>{} ... ); }

constexpr auto makeFooTuple ()
 { return makeFooTupleH(
      typename fooIndexList<0U, 100U, std::index_sequence<>>::type {}); }


int main ()
 {
   auto ft = makeFooTuple();

   static_assert( std::is_same<decltype(ft),
                  std::tuple<foo<2U>, foo<3U>, foo<5U>, foo<7U>>>{}, "!");
 }

Limits:

  • this solution works only if the generic foo isn't defined
  • the code is C++14; if you need it in C++11 it's a little more complicated
  • the upper limit in makeFooTuple() can't be a big number because fooIndexList is recursive so is limited by the compiler's recursion limit; you can bypass this limit but require mode code.
like image 146
max66 Avatar answered Nov 12 '22 20:11

max66