std::function 带有模板参数
std::function with templated arguments
我想编写一个模板函数,将某些函数应用于来自两个向量的元素对。结果应该是一个新的结果向量。我希望这是一个模板化函数,以便它适用于不同类型。
我之前试过这个定义。但是,当我尝试将它应用于某个特定函数时,出现编译错误。
#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;
}
类型推导有什么问题?
你遇到了先有鸡还是先有蛋的问题。
T3
输入
template<typename T1, typename T2, typename T3>
T3 mapzip2(const vector<T1> &xs, const vector<T2> &ys, std::function<T3(T1, T2)> l)
必须从第三个参数推导出一个std::function<T3(T1, T2)>
但是当你打电话时
bool feq(double a, double b) {
return abs(a - b) < PRECISION;
}
// ...
vector<bool> result = mapzip2(a, b, feq);
你用 feq
调用 mapzip()
可以转换为 std::function<bool(double, double)>
但 不是 std::function<bool(double, double)>
所以 T3
类型不能推导为 bool
因为要将 feq
转换为 std::function<bool(double, double)>
你必须知道,before推导,即T3
是bool
。
可能的解决方案:
(1) 显式模板类型调用 mapzip()
vector<bool> result = mapzip2<double, double, bool>(a, b, feq);
这样编译器就知道T3
是bool
,所以把feq
转换成std::function<bool(double, double)>
(2) 用feq
构造一个std::function<bool(double, double)>
vector<bool> result = mapzip2(a, b, std::function<double, double, bool>{feq});
因此编译器可以接收 std::function
作为第三个参数并从中推导出 T3
。
(3)(更灵活,恕我直言,三者中最好的)避免 std::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;
}
观察decltype()
的使用推导returned向量的类型(旧的T3
)和auto
的使用(从C+开始+14) 对于函数的 returned 类型。
如果你不能使用C++14(只能使用C++11),你必须添加尾随return类型
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]))>
{
}
还请注意——正如 ypnos 在评论中指出的那样——你原来 mapzip2()
的签名是错误的:你 return result
,一个 std::vector<T3>
, 而不是 T3
.
问题在于模板函数不推断类型也不进行隐式转换(如果您不提供类型,则让编译器生成函数)。编译器只是试图找到一个简单的匹配。考虑这个例子:
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;
}
main
中的第二个赋值将发出 no matching function for call to ‘add2(double, int)’
错误。
在您的情况下,您传递 feq
类型 bool (*)(double, double)
,也就是说,它是一个函数指针,而 mapzip2
需要 std::function
对象。模板没有隐式转换。
正如其他人所建议的,您可以显式构建函数对象。
(也正如其他人指出的那样,您需要 return vector<T3>
,而不仅仅是 T3
,但这是第二个问题,与原始问题无关)。
最后,如果您确实提供了模板类型,编译器确实会尝试隐式转换,例如,在上面的示例中,以下内容将起作用:
double b = add2<double>(10.2, 20);
标准库使用迭代器解决了这个问题。最好也使用它们,因为您的代码具有与标准算法相同的结构:
// 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);
或者直接使用std::transform()
.
如果您仍想 return 来自函数的向量,这将有助于将 return 类型推导与函数本身分离:
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;
}
我想编写一个模板函数,将某些函数应用于来自两个向量的元素对。结果应该是一个新的结果向量。我希望这是一个模板化函数,以便它适用于不同类型。
我之前试过这个定义。但是,当我尝试将它应用于某个特定函数时,出现编译错误。
#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;
}
类型推导有什么问题?
你遇到了先有鸡还是先有蛋的问题。
T3
输入
template<typename T1, typename T2, typename T3>
T3 mapzip2(const vector<T1> &xs, const vector<T2> &ys, std::function<T3(T1, T2)> l)
必须从第三个参数推导出一个std::function<T3(T1, T2)>
但是当你打电话时
bool feq(double a, double b) {
return abs(a - b) < PRECISION;
}
// ...
vector<bool> result = mapzip2(a, b, feq);
你用 feq
调用 mapzip()
可以转换为 std::function<bool(double, double)>
但 不是 std::function<bool(double, double)>
所以 T3
类型不能推导为 bool
因为要将 feq
转换为 std::function<bool(double, double)>
你必须知道,before推导,即T3
是bool
。
可能的解决方案:
(1) 显式模板类型调用 mapzip()
vector<bool> result = mapzip2<double, double, bool>(a, b, feq);
这样编译器就知道T3
是bool
,所以把feq
转换成std::function<bool(double, double)>
(2) 用feq
std::function<bool(double, double)>
vector<bool> result = mapzip2(a, b, std::function<double, double, bool>{feq});
因此编译器可以接收 std::function
作为第三个参数并从中推导出 T3
。
(3)(更灵活,恕我直言,三者中最好的)避免 std::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;
}
观察decltype()
的使用推导returned向量的类型(旧的T3
)和auto
的使用(从C+开始+14) 对于函数的 returned 类型。
如果你不能使用C++14(只能使用C++11),你必须添加尾随return类型
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]))>
{
}
还请注意——正如 ypnos 在评论中指出的那样——你原来 mapzip2()
的签名是错误的:你 return result
,一个 std::vector<T3>
, 而不是 T3
.
问题在于模板函数不推断类型也不进行隐式转换(如果您不提供类型,则让编译器生成函数)。编译器只是试图找到一个简单的匹配。考虑这个例子:
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;
}
main
中的第二个赋值将发出 no matching function for call to ‘add2(double, int)’
错误。
在您的情况下,您传递 feq
类型 bool (*)(double, double)
,也就是说,它是一个函数指针,而 mapzip2
需要 std::function
对象。模板没有隐式转换。
正如其他人所建议的,您可以显式构建函数对象。
(也正如其他人指出的那样,您需要 return vector<T3>
,而不仅仅是 T3
,但这是第二个问题,与原始问题无关)。
最后,如果您确实提供了模板类型,编译器确实会尝试隐式转换,例如,在上面的示例中,以下内容将起作用:
double b = add2<double>(10.2, 20);
标准库使用迭代器解决了这个问题。最好也使用它们,因为您的代码具有与标准算法相同的结构:
// 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);
或者直接使用std::transform()
.
如果您仍想 return 来自函数的向量,这将有助于将 return 类型推导与函数本身分离:
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;
}