Is it possible in C++ to have a variable number of parameters be specialized by the template value?

93 views Asked by At

Say I have the following template class:

template<int R> class dummy{
    public:
        // functions
    private:
        int arr[R];
};

Is it possible to create a constructor that accepts exactly R number of parameters that are to be used to initialize the array?

Something used like this:

dummy<3> a(1,2,3); // where array[0] is set to 1, array[1] is set to 2, and so on...

I know how to achieve the same result with this:

template<int R> class dummy{
    public:
        dummy(const int(&list)[R]){
            for (int i = 0; i < R; i++) arr[i] = list[i];
        }
    private:
        int arr[R];
};

Then use it:

dummy<3> a({1,2,3});    // OK
dummy<3> b({1,2});      // also OK but arr[2] will be set to 0
dummy<3> c({1,2,3,4});  // compile-time error

The issue is I want a compile time guarantee that the list being passed is exactly the size R, not bigger, not smaller. The compiler for the above code will complain if the list is larger, but not if smaller.

I saw this answer but the size there is automatically determined by the function call and the hardcoded value of 3 is part of the assertion. I can't use static_assert because R will always be the value set by the class initialization (e.g., dummy<3> sets R=3 and the constructor call will fill the list with 0s if it is smaller than 3).

How to guarantee the number of passed arguments is exactly R at compile time?

3

There are 3 answers

1
273K On BEST ANSWER

The static_assert() works well if you make the constructor parameterized. Working example: https://godbolt.org/z/dcP683cdh

#include <cstddef>

template <int R>
class dummy {
 public:
  template <size_t N>
  explicit dummy(const int (&list)[N]) {
    static_assert(N == R);
    for (int i = 0; i < R; i++)
      arr[i] = list[i];
  }

 private:
  int arr[R];
};

dummy<3> a({1, 2, 3});
dummy<3> b({1, 2});
dummy<3> c({1, 2, 3, 4});

int main() {}
2
Everyone On

I am posting this answer based on the feedback from Evg as the alternative solution other than the accepted one, in case somebody wanted the other method that creates a constructor with exactly R parameters:

template<int R> class dummy {
    public:
      dummy<R>() {}
        template<class... T, typename = std::enable_if_t<sizeof...(T) == R>> dummy(T... args) {
            size_t i = 0; 
            // iterate through the arguments method - useful if using types other than int[] as the data behind
            for (const auto val : {args...}) {
              arr[i++] = val;
            }
        }

    private:
      int arr[R];
};

Then it can be used like this:

dummy<5> a(1,2,3,4,5);    // OK
dummy<5> b(1,2,3,4,5,6);  // COMPILER ERROR
dummy<5> c(1,2,3,4);      // COMPILER ERROR
2
Red.Wave On

Using C++20 concepts, you can achieve it without using raw arrays at all,:

#include <concepts>
template<std::size_t N>
class dummy{
public:
    template<std::convertible_to<int> ... Targs>
        requires (N == sizeof...(Targs))
    dummy::dummy(Targs&& ... vargs)
    : arr{std::forward<Targs>(vargs)...} {};
private:
    std::array<int, N> arr;
};

template<std::convertible_to<int> ... Targs>
dummy(Targs&& ... vargs)->dummy<sizeof...(Targs)>;// CTAD

dummy a{1,2,3};         //CTAD: dummy<3> a;
auto  b = dummy{1,2};   //CTAD: dummy<2> b;
dummy<5> c {1,2,3,4,5}; //explicit construction 
//dummy<4> d{1,2};      //compile error

The construction syntax becomes a bit cleaner.