Yauhen Yakimenka
Yauhen Yakimenka

Reputation: 499

std::function with templated arguments

I want to write a templated function that applies some function over pairs of elements coming from two vectors. The result should be a new vector of results. I want this to be a templated function so that it works on different types.

I tried the definition before. However, when I try to apply it to some particular function, I get a compilation error.

#include <vector>
#include <cmath>
#include <iostream>
#include <functional>

using namespace std;

template<typename T1, typename T2, typename T3>
vector<T3> mapzip2(const vector<T1> &xs, const vector<T2> &ys, std::function<T3(T1, T2)> l) {
    if (xs.size() != ys.size())
        throw runtime_error("mapzip2: container sizes (" + to_string(xs.size()) + 
                            " and " + to_string(ys.size()) + ") do not match");
    vector<T3> result;
    result.reserve(xs.size());
    for (int i = 0; i < xs.size(); i++)
        result.push_back(l(xs[i], ys[i]));

    return result;
}

constexpr double PRECISION = 1E-6;

bool feq(double a, double b) {
    return abs(a - b) < PRECISION;
}

int main() {
    vector<double> a = {0.3, 0.42, 0.0, -7.34};
    vector<double> b = {0.3, 0.42, 0.0, -7.34};

    // compilation error: no matching function for call to 
    // ‘mapzip2(std::vector<double>&, std::vector<double>&, bool (&)(double, double))’
    vector<bool> result = mapzip2(a, b, feq);

    for (bool b: result) cout << b << ' ';
    cout << endl;
}

What is wrong here with types deduction?

Upvotes: 1

Views: 316

Answers (3)

David G
David G

Reputation: 96845

The Standard Library solves this issue by using iterators. It would be a good idea to use them too since your code has the same structure as a standard algorithm:

// Overload #1
template<class I1, class I2, class O, class F>
void zip(I1 begin1, I1 end1, I2 begin2, O out_it, F f) {
  while (begin1 != end1) {
    out_it++ = f(*begin1++, *begin2++);
  }
}

// Overload #2
template<class C1, class C2, class R, class F>
void zip(C1& c1, C2& c2, R& ret, F f) {
  using std::begin; using std::end;
  zip(begin(c1), end(c1), begin(c2), std::back_inserter(ret), f);
}

vector<bool> result;
zip(a, b, result, feq);

Or just use std::transform().

If you still want to return the vector from the function, it would help to decouple the return type deduction from the function itself:

template<class T> using value_t = typename std::decay_t<T>::value_type;
template<class F,class... Cs> using zip_ret = std::result_of_t<F&(value_t<Cs>...)>;

template<class C1, class C2, class F, class R=zip_ret<F, C1, C2>>
std::vector<R> zip(C1& c1, C2& c2, F f) {
    using std::begin; using std::end;
    std::vector<R> ret;
    std::transform(begin(c1), end(c1), begin(c2), std::back_inserter(ret), f);
    return ret;
}

Upvotes: 0

max66
max66

Reputation: 66230

You have a sort of chicken-and-egg problem.

The T3 type in

template<typename T1, typename T2, typename T3>
T3 mapzip2(const vector<T1> &xs, const vector<T2> &ys, std::function<T3(T1, T2)> l)

has to be deduced from the third argument, a std::function<T3(T1, T2)>

But when you call

bool feq(double a, double b) {
    return abs(a - b) < PRECISION;
}

// ...

    vector<bool> result = mapzip2(a, b, feq);

you call mapzip() with feq that can be converted to a std::function<bool(double, double)> but isn't a std::function<bool(double, double)>

So the T3 type can't be deduced as bool because to convert feq to std::function<bool(double, double)> you have to know, before the deduction, that T3 is bool.

Possible solutions:

(1) explicit the template types calling mapzip()

vector<bool> result = mapzip2<double, double, bool>(a, b, feq);

This way the compiler know that T3 is bool, so convert feq to std::function<bool(double, double)>

(2) construct a std::function<bool(double, double)> with feq

vector<bool> result = mapzip2(a, b, std::function<double, double, bool>{feq});

so the compiler can receive a std::function as third argument and deduce T3 from it.

(3) (more flexible and, IMHO, the best of the three) avoid std::function and use a more generic functional typename for the function

template<typename T1, typename T2, typename F>
auto mapzip2(const vector<T1> &xs, const vector<T2> &ys, F l) {
    if (xs.size() != ys.size())
        throw runtime_error("mapzip2: container sizes (" + to_string(xs.size()) + 
                            " and " + to_string(ys.size()) + ") do not match");

    vector<decltype(l(xs[0], ys[0]))> result; // <-- use decltype() !

    result.reserve(xs.size());
    for (int i = 0; i < xs.size(); i++)
        result.push_back(l(xs[i], ys[i]));

    return result;
}

Observe the use of decltype() to deduce the type of the returned vector (the old T3) and the use of auto (starting from C++14) for the returned type of the function.

If you can't use C++14 (only C++11), you have to add the trailing return type

template<typename T1, typename T2, typename F>
auto mapzip2(const vector<T1> &xs, const vector<T2> &ys, F l) 
    -> std::vector<decltype(l(xs[0], ys[0]))>
 {

 }

Observe also -- as pointed by ypnos in a comment -- that the signature of your original mapzip2() is wrong: you return result, a std::vector<T3>, not a T3.

Upvotes: 4

Israel Unterman
Israel Unterman

Reputation: 13520

The problem is that template functions don't infer types and don't do implicit casting (if you don't supply the types, just let the compiler generate the function). The compiler just tries to find a simple match. Consider this example:

template<typename T>
T add2(T a, T b)
{
    T res = a + b;
    return res;
}


int main()
{
    int a = add2(10, 20);  // ok
    double b = add2(10.2, 20); // error, no implicit cast from int to double

    return 0;
}

The second assignment in main will emit no matching function for call to ‘add2(double, int)’ error.

As in your case, you pass feq which is of type bool (*)(double, double), that is, it's a function pointer, while mapzip2 expects std::function object. There is no implicit casting for templates.

As others suggested, you can build the function object explicitly. (also as others noted, you need to return vector<T3>, not just T3, but this is a second problem, not related to the original one).

Finally, if you do supply the template types, the compiler will indeed try implicit casting, for example, in the above example, the following will work:

double b = add2<double>(10.2, 20);

Upvotes: 1

Related Questions