5

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?

max66
  • 65,235
  • 10
  • 71
  • 111
  • What is `ItemID`? Seems like `ItemTemplate<-1>` would not trigger the static assert. An [mcve] would be useful here. – Sam Varshavchik Sep 11 '17 at 03:12
  • It's std::uint32, and I don't think that's the point @Sam Varshavchik – 炸鱼薯条德里克 Sep 11 '17 at 03:15
  • No, that's precisely the point. Using an `unsigned int` for an `ItemId`, `std::tuple, ItemTemplate<5>>` throws a compilation error, since it obviously hits the static assert. Your question is unclear. If your question is really about how to enumerate all available specializations automatically, this is not possible in C++. You will have to explicitly implement this yourself. – Sam Varshavchik Sep 11 '17 at 03:18
  • Are the defined `ItemTemplate` versions guaranteed to be contiguous, as in `1, 2, 3 ... n`, or might you define `ItemTemplate<1>` and, for instance, `ItemTemplate<6>` without defining anything in between? – Silvio Mayolo Sep 11 '17 at 03:18
  • No, They are not guaranteed to be contiguous. I might use any non-negetive integer samller than 0xFFFFFFFF. @SilvioMayolo – 炸鱼薯条德里克 Sep 11 '17 at 03:26
  • So How to improve this ,add static_cast(item_id) == -1 ? And it is about to enumerate all available specializations automatically. I only wanna allow what I already defined, undefined values are expected to cause compile error.@SamVarshavchik May be an X-Y problem, though... – 炸鱼薯条德里克 Sep 11 '17 at 03:28
  • It is not possible to enumerate all existing specializations, in C++. You'll have to keep track of the specializations yourself. Even if the IDs were continuous, the static assert seems to prevent any kind of an SFINAE-based approach to enumerating the contiguous range. Which would also prevent SFINAE from enumerating the entire 32-bit range, via a brute-force solution, even if having the compiler to take days (weeks?) to churn through the 32-bit range isn't an issue. – Sam Varshavchik Sep 11 '17 at 03:48
  • Maybe another question, AFAIK, template and constexpr can be used to enumerating the entire 32-bit range(Although I think this will kill the compiler). But how to test exsitence of template spectialization? like, is_defined_v<1 or 2> returns true, is_defined_v<3 or 5 or 9> returns false? – 炸鱼薯条德里克 Sep 11 '17 at 04:17
  • template struct is_defined : false_type {}; template struct is_defined>> : true_type {}; template constexpr bool is_defined_v = is_defined::value; This doesn't work(It always returns true). – 炸鱼薯条德里克 Sep 11 '17 at 04:22
  • 1
    [How to decide if a template specialization exist](https://stackoverflow.com/questions/44229676/how-to-decide-if-a-template-specialization-exist) – Axalo Sep 11 '17 at 04:23
  • This approach to determine if a template specialization exists will not work in this case because of the static assert. A static assert cannot be used with template metaprogramming. The End. – Sam Varshavchik Sep 11 '17 at 04:32
  • 1
    Remove the implementation of `ItemTemplate`. Just declare it like `template struct ItemTemplate;`. No need for static_assert. Provide definition only for specialized classes. Then when ever you instantiate `ItemTemplate` with some integer for which a specialization is not provided you get an error. – army007 Sep 11 '17 at 06:41

1 Answers1

2

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.
max66
  • 65,235
  • 10
  • 71
  • 111