Reputation: 499
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
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
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
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