BenLand100
BenLand100

Reputation: 23

Expand parameter pack in std::function template

I'm working on some shorthand functional programming methods to aid in data analysis in C++ and I ran into a situation where I feel like my implmentation should work but g++ disagrees with me. See the following code:

#include <algorithm>
#include <valarray>
#include <functional>
#include <iostream>

using namespace std;

//generates a list of [from,to] in increments of step. last is <= to with precision of step
template<typename T> std::valarray<T> range(T from, T to, T step = 1) {
    size_t elems = (size_t)floor((to-from)/step) + 1;
    std::valarray<T> result(elems);
    for (int i = 0; i < elems; i++) {
        result[i] = from+step*i;
    }
    return result;
}

//map over multiple lists as arguments to the provided function
template<typename T, typename... Ts> void mapthreadv(std::function<void(T,Ts...)> func, std::valarray<T> &in, std::valarray<Ts>&... rest) {
    for (int i = 0; i < in.size(); i++) {
        func(in[i],rest[i]...);
    }
}

int main(int argc, char **argv) {  
    auto first = range(0.0,1.0,0.1);
    auto second = range(0.0,10.0,1.0);
    auto third = range(0.0,100.0,10.0);
    mapthreadv<double,double,double>([](double a, double b, double c) { cout << '{' << a << ',' << b << ',' << c << "},"; },first,second,third);
}   

Expected output would be:

{0,0,0},{0.1,1,10},{0.2,2,20},{0.3,3,30},{0.4,4,40},{0.5,5,50},{0.6,6,60},{0.7,7,70},{0.8,8,80},{0.9,9,90},{1,10,100},

Which is achievable by directly specifying <void(double,double,double)> instead of <void(T,Ts...)> to std::function, however this is obviously not a useful fix. The code fails to compile as written, and the error is related to template argument deduction/substitution:

‘main(int, char**)::<lambda(double, double, double)>’ is not derived from ‘std::function<void(double, Ts ...)>’

So my gut feeling is that for some reason Ts is not being expanded... any pointers or obvious oversights on my part? I'm quite new to template functions in general so any help is appreciated.

Upvotes: 2

Views: 977

Answers (1)

T.C.
T.C.

Reputation: 137310

The problem is that template argument deduction is still performed when you use a template parameter pack, even if you explicitly specify the types (§ 14.8.1 [temp.arg.explicit]/p9):

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments. [ Example:

template<class ... Types> void f(Types ... values);
void g() {
    f<int*, float*>(0, 0, 0);
}
// Types is deduced to the sequence int*, float*, int

end example ]

And, since a lambda closure type is not a std::function, template argument deduction will fail.

There is no reason to use std::function here anyway; you can simply take the functor as a template parameter:

template<typename F, typename T, typename... Ts> void mapthreadv(F func, std::valarray<T> &in, std::valarray<Ts>&... rest) {
    for (int i = 0; i < in.size(); i++) {
        func(in[i],rest[i]...);
    }
}

which also obviates the need to explicitly specify template arguments:

mapthreadv([](double a, double b, double c) { std::cout << '{' << a << ',' << b << ',' << c << "},"; },first,second,third);

Demo.

Upvotes: 3

Related Questions