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?
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
.