Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

variadic templates same number of function arguments as in class

How to define method signature so it will accept same number of arguments as variadic template class definition? For example how to define an Array class:

template<typename T, int... shape>
class Array
{
public:
    T& operator () (???);
};

So you will be able to call it like this:

Array<int, 3, 4, 5> a;
a(1, 2, 3) = 2;
like image 352
DikobrAz Avatar asked Nov 05 '14 20:11

DikobrAz


People also ask

Can template function take any number of arguments?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration.

What is special about Variadic functions?

Variadic functions are functions that can take a variable number of arguments. In C programming, a variadic function adds flexibility to the program. It takes one fixed argument and then any number of arguments can be passed.

What is Variadic template in C++?

A variadic template is a class or function template that supports an arbitrary number of arguments. This mechanism is especially useful to C++ library developers: You can apply it to both class templates and function templates, and thereby provide a wide range of type-safe and non-trivial functionality and flexibility.

Is printf variadic function?

The C printf() function is implemented as a variadic function. This noncompliant code example swaps its null-terminated byte string and integer parameters with respect to how they are specified in the format string.


2 Answers

template<class T, int...Shape>
class Array {
  template<int>using index_t=int; // can change this
public:
  T& operator()(index_t<Shape>... is);
};

or:

template<class T, int...Shape>
class Array {
public:
  T& operator()(decltype(Shape)... is);
};

or:

template<class T, int...Shape>
class Array {
public:
  T& operator()(decltype(Shape, int())... is);
};

if you want to be able to change the type of the parameter to be different than Shape.

I find the decltype harder to understand a touch than the using, especially if you want to change the type of the parameter to be different than int.

Another approach:

template<class T, int...Shape>
class Array {
public:
  template<class...Args,class=typename std::enable_if<sizeof...(Args)==sizeof...(Shape)>::type>
  T& operator()(Args&&... is);
};

which uses SFINAE. It does not enforce that the Args are integer types however. We could add another clause if we wanted to (that all of the Args are convertible to int, say).

Yet another approach is to have your operator() take a package of values, like a std::array<sizeof...(Shape), int>. Callers would have to:

Array<double, 3,2,1> arr;
arr({0,0,0});

use a set of {}s.

A final approach would be:

template<class T, int...Shape>
class Array {
public:
  template<class...Args>
  auto operator()(Args&&... is) {
    static_assert( sizeof...(Args)==sizeof...(Shapes), "wrong number of array indexes" );
  }
};

where we accept anything, then generate errors if it is the wrong number of arguments. This generates very clean errors, but does not do proper SFINAE operator overloading.

I would recommend tag dispatching, but I don't see a way to make it much cleaner than the SFINAE solution, with the extra decltype and all, or better error messages than the static_assert version on the other hand.

like image 170
Yakk - Adam Nevraumont Avatar answered Nov 15 '22 16:11

Yakk - Adam Nevraumont


I assume you want your arguments to be all of the same type, probably using an integer type (I'll just use int). An easy approach is to leverage the parameter pack you already have:

template <int>
struct shape_helper { typedef int type; };

template <typename T, int... Shape>
class Array
{
public:
    T& operator()(typename shape_helper<Shape>::type...);
}; 
like image 20
Dietmar Kühl Avatar answered Nov 15 '22 16:11

Dietmar Kühl