1

Given the example below, what's the best way to condense the template specializations such that one or two instructions would suffice to define all of the special values? Variadics, perhaps?

enum class PositiveDigit // Not necessarily sequential
{ One=1, Two, Three, Four, Five, Six, Seven, Eight, Nine };

// Base class template
template <std::underlying_type_t<PositiveDigit>>
struct IsNum
{ static constexpr bool aPrimeDigit = false; };

// Specialized class templates to define which positive digits are prime 
// HOW TO BEST COMBINE/CONDENSE THESE? VARIADIC?? Ideally, something like:
//    template <PositiveDigit::Two, PositiveDigit::Three, ……> ……   OR,
//    template <> …… (PositiveDigit::Two, PositiveDigit::Three, ……) ……   OR??
template <>
struct IsNum<static_cast<std::underlying_type_t<PositiveDigit>>(PositiveDigit::Two)>
{ static constexpr bool aPrimeDigit = true; };
template <>
struct IsNum<static_cast<std::underlying_type_t<PositiveDigit>>(PositiveDigit::Three)>
{ static constexpr bool aPrimeDigit = true; };
template <>
struct IsNum<static_cast<std::underlying_type_t<PositiveDigit>>(PositiveDigit::Five)>
{ static constexpr bool aPrimeDigit = true; };
template <>
struct IsNum<static_cast<std::underlying_type_t<PositiveDigit>>(PositiveDigit::Seven)>
{ static constexpr bool aPrimeDigit = true; };

int main() {
    // Note: It's perfectly okay to pass integers beyond the range of the
    //  enum class, they'll simply provide a false result
    IsNum<-5>::aPrimeDigit; // false
    IsNum<13>::aPrimeDigit; // false
    IsNum< 7>::aPrimeDigit; // true!
}

Please assume as given that the enum must remain strongly-typed. The real-world issue has a large enum class, many potential specializations, and has nothing whatsoever to do with digits or primes; this was just a simple example.

These similar questions don't appear to address the issue at hand (unless I'm missing something):

2 Answers2

1

Are you looking for something like this?

C++17:

#include <type_traits>

template <auto Value, decltype(Value)... List>
struct value_in_list
    : public std::disjunction<std::bool_constant<Value==List>...> {};

template <std::underlying_type_t<PositiveDigit> N>
struct IsNum
{
    static constexpr bool aPrimeDigit =
        value_in_list<static_cast<PositiveDigit>(N),
            PositiveDigit::Two, PositiveDigit::Three,
            PositiveDigit::Five, PositiveDigit::Seven
        >::value;
};

Or C++14:

#include <type_traits>

template <typename T, T Value, T... List>
struct value_in_list;

// Base case 1: Value is not in an empty list.
template <typename T, T Value>
struct value_in_list<T, Value> : public std::false_type {};

// Base case 2: Value is in a list that starts with Value.
template <typename T, T Value, T... Rest>
struct value_in_list<T, Value, Value, Rest...>
    : public std::true_type {};

// Recursion case: If a non-empty list does not start with Value,
// Value is in the list if and only if it's in the list with the
// first element removed.
template <typename T, T Value, T First, T... Rest>
struct value_in_list<T, Value, First, Rest...>
    : public value_in_list<T, Value, Rest...> {};

template <std::underlying_type_t<PositiveDigit> N>
struct IsNum
{
    static constexpr bool aPrimeDigit =
        value_in_list<PositiveDigit, static_cast<PositiveDigit>(N),
            PositiveDigit::Two, PositiveDigit::Three,
            PositiveDigit::Five, PositiveDigit::Seven
        >::value;
};
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • My compiler (VS2017, which only partially supports C++17) doesn't like the `auto` and I'm not yet fluent enough in templates to figure a workaround. Any ideas? – quasinormalized Jun 30 '18 at 18:17
  • Added a C++14 solution. Without C++17's `auto` template parameters, you generally have to make the type and the value into two separate template parameters. – aschepler Jun 30 '18 at 20:31
  • This is a very good & instructive answer. I am accepting max66's C++14 compliant answer only for its brevity. Both of these answers have furthered my understanding of templates. Cheers! – quasinormalized Jul 02 '18 at 02:46
1

Just to make fun with variadic templates, I propose a couple of solutions.

Both of they are based over a constexpr function that say if a value is in a template variadic list (as value_in_list in aschepler's answer but this works also in C++14)

template <typename T, T ... ts>
constexpr bool isInList (T const & t0)
 {
   using unused = bool[];

   bool ret { false };

   (void)unused { false, ret |= t0 == ts... };

   return ret;
 }

The first one is very similar to the aschepler's solution (+1) and doesn't use template specialization.

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum1
{ 
   static constexpr bool aPrimeDigit
      = isInList<PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
                 PositiveDigit::Five, PositiveDigit::Seven>
                    (static_cast<PositiveDigit>(I));
};

It seems to me the simpler one but if you really (really!) want to pass through template specialization, you can write something as follows

template <std::underlying_type_t<PositiveDigit>, typename = std::true_type>
struct IsNum2
 { static constexpr bool aPrimeDigit = false; };

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum2<I, std::integral_constant<bool, isInList<
   PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
   PositiveDigit::Five, PositiveDigit::Seven>
      (static_cast<PositiveDigit>(I))>>
 { static constexpr bool aPrimeDigit = true; };

The following is a full compiling example

#include <type_traits>

enum class PositiveDigit
{ Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine };

template <typename T, T ... ts>
constexpr bool isInList (T const & t0)
 {
   using unused = bool[];

   bool ret { false };

   (void)unused { false, ret |= t0 == ts... };

   return ret;
 }

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum1
{ 
   static constexpr bool aPrimeDigit
      = isInList<PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
                 PositiveDigit::Five, PositiveDigit::Seven>
                    (static_cast<PositiveDigit>(I));
};

template <std::underlying_type_t<PositiveDigit>, typename = std::true_type>
struct IsNum2
 { static constexpr bool aPrimeDigit = false; };

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum2<I, std::integral_constant<bool, isInList<
   PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
   PositiveDigit::Five, PositiveDigit::Seven>
      (static_cast<PositiveDigit>(I))>>
 { static constexpr bool aPrimeDigit = true; };

int main ()
 {
   static_assert( false == IsNum1<-5>::aPrimeDigit, "!" );
   static_assert( false == IsNum1<13>::aPrimeDigit, "!" );
   static_assert(  true == IsNum1< 7>::aPrimeDigit, "!" );

   static_assert( false == IsNum2<-5>::aPrimeDigit, "!" );
   static_assert( false == IsNum2<13>::aPrimeDigit, "!" );
   static_assert(  true == IsNum2< 7>::aPrimeDigit, "!" );
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • No, specialization is not necessary, any concise solution will do. And your first suggestion is certainly effective (and succinct). I'm confused, though, about how the parameter pack makes its way into `isInList()` without `ts` existing in the function arguments. Can you expound on that idiom or point me to a good resource? Also, know of any clever ways to avoid having an unused array? My OCD insisted I ask. – quasinormalized Jul 02 '18 at 01:28
  • @quasinormalized - `ts...` exist as template parameter; you can, also in C++11 and in C++14, unpack a variadic list inside an array initialization. So -- by example -- given a `ts...` equal to `2, 3, 5, 7`, the initialization become `(void)unused { false, ret |= t0 == 2, ret |= t0 == 3, ret |= t0 == 5, ret |= t0 == 7 };`. Observe the first `false`: it's necessary in case `ts...` is an empty variadic list. Unfortunately a function with a variable can't be `constexpr` in C++11 so this solution can work only starting from C++14. (continue). – max66 Jul 02 '18 at 01:59
  • @quasinormalized - Yes: I know a way to avoid the `unused` array. You can use recursion (also in C++11) or simply write (caution: code not tested) `template constexpr bool isInList (T const & t0) { return ( (t0 == ts) || ... ); }`. Observe that, substantially, is the first aschepler's solution (see `value_in_list`). It's called "folding". It's really elegant ans powerful. Unfortunately is available only starting from C++17. – max66 Jul 02 '18 at 02:06