Template specialization with Parameter Packs

668 Views Asked by At

I'm quite new to c++ but wanted to try to do some fancy template stuff. I am not sure if it is possible, but I am fairly confident there is a way to achieve that.

So here's the Issue: I am calling various Hardware function, which requires a number of parameters and only differ by one parameter type:

int Read(HANDLE handle, int location, char* Name, int startindex1, int endindex1,
         int startindex2, int endindex2, int* rval, Astruct* callback) ;
int Read(HANDLE handle, int location, char* Name, int startindex1, int endindex1,
         int startindex2, int endindex2, double* rval, Astruct* callback) ;

The Hardware Interface returns a scalar, array or matrix, depending on the values of the indexes. What i actually want to achieve, is a function, which returns an T or vector<T> or vector<vector<T>> dependening on the number of parameters I pass.:

                T MyReadFunction<T>(HANDLE handle, int location, char* Name,int index)
        vector<T> MyReadFunction<T>(HANDLE handle, int location, char* Name,int startindex1,
                                                                            int endindex1   )
vector<vector<T>> MyReadFunction<T>(HANDLE handle, int location, char* Name,int startindex1, 
                                                                            int endindex1
                                                                            int startindex2, 
                                                                            int endindex2)

where T is a basic type like int, real, float, double,etc.

Using 3 different templates with specializations is no issue, but I would love to combine it somehow.

My assumption is, that this can be achieve using template specializations, but I cant get my head around it. I think i should something like this:

template<typename T ,int... Indexes>
T MyReadFunction (HANDLE handle, int location, char* Name, Indexes... myIndex){}

template<>
int MyReadFunction (HANDLE handle, int location, char* Name, int myindex)
{
    int rval = 0; 
    Read (handle,location,name,myindex,myindey,0,0,&rval, NULL) ;
    return rval;
}

This comes as a two headed beast. I porperbly need to implement my 3 cases explicitly to avoid misusage, but also want to know, how i could achieve template specialization with variying size of the parameter pack, depending on the size of the pack.

I'am using msvc++ latest with VS 2019

2

There are 2 best solutions below

4
On BEST ANSWER

You can write a type that creates a nested vector of the desired rank, like this:

template<typename T,int N> 
struct VT 
{ 
    using type = typename std::vector<typename VT<T, N - 1>::type>;
};

template<typename T> 
struct VT<T, 0> 
{ 
    using type = int;
};

and then write a single function like this:

template<typename T ,typename ...Indexes>
typename VT<T, sizeof...(Indexes) / 2>::type 
  MyReadFunction (int handle, int location, char* Name, Indexes... myIndex) {}

Here's a demo.

As you've observed, there's not really much point in doing this, because the body of the function would become rather tricky to implement. Simply writing specializations, or overloads, would be a simpler solution.

Note that if you do choose the write this function template, the myIndex parameter pack is unconstrained, i.e. it will accept types other than int. Of course, you can constrain that by writing just a little more code.

Also, this function template will accept Indexes of size 3, 5, 7, etc, which is likely not what you want. Again, you can constrain that as well by writing a little more code.

1
On

cigien has a good answer for this question about parameter packs.

Here's another solution that doesn't use packs. Parameter packs are type-lists, but you don't actually need to vary over N types, you just want an N-dimensional array.

If you put the list of indexes inside curly braces, then you can treat it as a list with compile-time length.

It looks to me, though, like the scalar case may not generalize well from the multidimensional cases. The scalar case takes a single index, whereas the arrays need start and end indexes for each dimension.

template<typename T,int N> 
struct VT 
{
    using type = typename std::vector<typename VT<T, N - 1>::type>;
};

template<typename T> 
struct VT<T, 0> 
{ 
    using type = T;
};

template< typename T, int n >
std::enable_if_t< n == 1 || n % 2 == 0,
  typename VT<T, n/2>::type >
  MyReadFunction( HANDLE handle, int location, char* Name, int const (&indexes)[n] );

// usage: MyReadFunction< double >( inputFile, 123, "name", { 1, 2, 3, 4 } );
// yields vector< vector< double > >