即使以下程序中对fold_left
的调用似乎足以推断出模板化函数的参数,但由于template argument deduction失败而出现编译错误。
#include <valarray>
#include <functional>
template<class Input, class Output>
Output fold_left(Output zero, std::function<Output(Output,Input)> op, std::valarray<Input> data)
{
for (auto const& item : data) {
zero = op(zero, item);
}
return zero;
}
#include <iostream>
int main()
{
std::valarray<int> data { 1, 2, 3, 4, 5 };
std::cout << fold_left(0, std::plus<int>{}, data) << "\n";
}
main.cpp: In function 'int main()': main.cpp:17:66: error: no matching function for call to 'fold_left(int, std::plus<int>, std::valarray<int>&)' std::cout << fold_left/*<int,int>*/(0, std::plus<int>{}, data) << "\n"; ^ main.cpp:5:8: note: candidate: template<class Input, class Output> Output fold_left(Output, std::function<Output(Output, Input)>, std::valarray<_Tp>) Output fold_left(Output zero, std::function<Output(Output,Input)> op, std::valarray<Input> data) ^~~~~~~~~ main.cpp:5:8: note: template argument deduction/substitution failed: main.cpp:17:66: note: 'std::plus<int>' is not derived from 'std::function<Output(Output, Input)>' std::cout << fold_left/*<int,int>*/(0, std::plus<int>{}, data) << "\n"; ^
只有在调用fold_left
时才会编译此最小程序:
fold_left<int, int>(0, std::plus<int>{}, data);
这对我来说很奇怪,因为Output
模板参数应该是显而易见的,因为我们提供了int
作为第一个参数(zero
);类似地,Input
模板参数应该正确推导,因为我们提供了std::valarray<int>
作为期望data
的第三个参数(std::valarray<Input>
)。
fold_left
are not relevant定义中参数的顺序。
为什么模板参数推断在这里失败?
答案 0 :(得分:4)
由于某些类型std::plus<int>
std::function<O(O,I)>
不是O,I
的实例,因此它们位于推断的上下文中。
解决方案是使用新的模板参数替换std::function<O(O,I)>
,该模板参数本身仅受基于其可使用两个参数O,I
调用的要求而受约束,并且返回类型可转换为{{1 }}:
O
您可以将template<class Input, class Output, class F,
std::enable_if_t<std::is_convertible_v<
std::invoke_result_t<F&, Output, Input>,
Output>, int> = 0>
Output fold_left(Output zero, F op, std::valarray<Input> data)
{ ... }
参数单独包装在非推导的上下文中,但实际上并不需要op
提供的类型擦除,因此这是不必要的。
答案 1 :(得分:1)
这对我来说很奇怪,因为
Output
模板参数应该是显而易见的,因为我们提供了int
作为第一个参数
编译器将尝试解析Output
在其使用的每个位置。如果从不同参数中推导出来的类型不匹配,那么您将会产生歧义,导致演绎失败。
由于您在Output
和Output zero
中使用std::function<Output(Output,Input)> op
,编译器将使用0
和std::plus<int>
来尝试找出Output
是的。 std::plus<int>
不是std :: function`,因此无法确定类型,这就是替换失败的原因。
如果将std::plus<int>{}
转换为std::function
,则编译器可以推断出模板类型
std::cout << fold_left(0, std::function<int(int, int)>(std::plus<int>{}), data) << "\n";
答案 2 :(得分:1)
问题是std::function<Output(Output,Input)>
无法正确推断其模板参数,因为std::plus<int>
不是std::function
。
类型推导仅在模板类型与传递的参数匹配时才有效;它不会执行通过转换/构造完成的转换;而是采用整体类型。
在这种情况下,std::plus<int>
不一个std::function<int(int,int)>
,可兑换到std::function<int(int,int)>
。这就是导致演绎失败的原因。
替代方法是使用别名中断对类型推导的贡献,显式传递std::function
类型,或者将函数接受为不同的模板参数(实际上可能更有效)
<强> 1。使用别名
打破对类型推导的贡献如果您创建了该类型的别名,以便这些类型不会对演绎做出贡献,那么它可以保留第一个和第三个参数来确定std::function
的类型
(脱离我的头顶;未经测试)
template<typename T>
struct nodeduce{ using type = T; };
template<typename T>
using nodeduce_t = typename nodeduce<T>::type;
template<class Input, class Output>
Output fold_left(Output zero, std::function<nodeduce_t<Output>(nodeduce_t<Output>,nodeduce_t<Input>)> op, std::valarray<Input> data)
{
....
}
这是一种丑陋的方法,但它会禁用std::function
对推导做出贡献,因为类型是通过nodeduce_t
转换的。
<强> 2。传递为std::function
这是最简单的方法;你的调用代码将成为:
fold_left(0, std::function<int(int,int)>(std::plus<int>{}), data);
不可否认,它并不漂亮 - 但它可以解决扣除问题,因为std::function<int(int,int)>
可以直接匹配输入。
第3。传递为模板参数
这实际上有一些额外的性能优势;如果您接受参数作为模板类型,那么它还允许编译器更好地内联函数(std::function
存在问题的内容)
template<class Input, typename Fn, class Output>
Output fold_left(Output zero, Fn&& op, std::valarray<Input> data)
{
for (auto const& item : data) {
zero = op(zero, item);
}
return zero;
}
所有这些都是可以完成相同任务的替代方案。
值得一提的是,可以使用SFINAE对enable_if
或decltype
评估进行编译时验证:
template<class Input, typename Fn, class Output, typename = decltype( std::declval<Output> = std::declval<Fn>( std::declval<Output>, std::declval<Input> ), void())>
Output fold_left( ... ){ ... }
decltype(...)
语法确保表达式格式正确,如果是,则允许此函数用于重载解析。
修改:更新了decltype
评估,以测试Output
和Input
类型。使用C ++ 17,您还可以在std::is_convertible_v<std::invoke_result_t<F&, Output, Input>,Output>
中使用enable_if
,如Barry中其他答案所示。