Search code examples
c++variadic-templatestemplate-meta-programming

Class template parameter pack expansion for constructors


I'd like to make a class template RestrictedInteger that can only be constructed with certain values known at compile time. This is how I could do it manually:

// Wrapper
template<int... Is> using IntList = std::integer_sequence<int, Is...>;

// This is my class
template<class intList> class RestrictedInteger;
template<int I1>
class RestrictedInteger<IntList<I1>> {
  const int _i;
public:
  constexpr RestrictedInteger(std::integral_constant<int, I1>) : _i(I1) {}
};
//[...]
template<int I1, I2, I3>
class RestrictedInteger<IntList<I1, I2, I3>> {
  const int _i;
public:
  constexpr RestrictedInteger(std::integral_constant<int, I1>) : _i(I1) {}
  constexpr RestrictedInteger(std::integral_constant<int, I2>) : _i(I2) {}
  constexpr RestrictedInteger(std::integral_constant<int, I3>) : _i(I3) {}
};
//[...] (and so on)

Naturally, I'd like to use a variadic template instead. If only this were legal:

template<int... Is>
class RestrictedInteger<IntList<Is...>> {
  int _i;
public:
  constexpr RestrictedInteger(std::integral_constant<int, Is>) : _i(Is) {}... // ERROR
}

Since I'm using C++17 however, I thought it would work like this:

template<int... Is>
class RestrictedInteger<IntList<Is...>> {
  int _i;
public:
  template<int I>
  constexpr RestrictedInteger(std::enable_if_t<...||(I==Is), std::integral_constant<int, I>>) : _i(I) {} // syntax error: '...' (Visual Stuio 2019)
};

But apparently not.

Any ideas of a neat way to solve this?


Solution

  • If failing compilation is an option (you don't need compiler to find other overloads) - you can put static_assert inside your constructor:

    #include <type_traits>
    #include <utility>
    
    template<int... Is> using IntList = std::integer_sequence<int, Is...>;
    
    template<class intList> class RestrictedInteger;
    
    template<int... Is>
    class RestrictedInteger<IntList<Is...>> {
    private:
      const int _i;
    public:
      template <int I>
      constexpr RestrictedInteger(std::integral_constant<int, I>) : _i(I) 
      {
          static_assert(((I == Is) || ...), "Invalid value");
      }
    };
    
    int main()
    {
        RestrictedInteger<IntList<1, 2, 3>> i = std::integral_constant<int, 3>();
        RestrictedInteger<IntList<1, 2, 3>> ii = std::integral_constant<int, 6>(); // fails
    }
    

    or a bit more verbose solution with std::enable_if :

    #include <type_traits>
    #include <utility>
    
    template<int... Is> using IntList = std::integer_sequence<int, Is...>;
    
    template<class intList> class RestrictedInteger;
    
    template<int... Is>
    class RestrictedInteger<IntList<Is...>> {
    private:
      const int _i;
    public:
      template <int I, typename std::enable_if_t<((I == Is) || ...)>* = nullptr>
      constexpr RestrictedInteger(std::integral_constant<int, I>) : _i(I) 
      {
      }
    };
    
    int main()
    {
        RestrictedInteger<IntList<1, 2, 3>> i = std::integral_constant<int, 3>();
        RestrictedInteger<IntList<1, 2, 3>> ii = std::integral_constant<int, 6>(); // fails
    }