模板参数推断失败

时间:2017-09-20 13:53:17

标签: c++ templates language-lawyer

即使以下程序中对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定义中参数的顺序。

为什么模板参数推断在这里失败?

3 个答案:

答案 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在其使用的每个位置。如果从不同参数中推导出来的类型不匹配,那么您将会产生歧义,导致演绎失败。

由于您在OutputOutput zero中使用std::function<Output(Output,Input)> op,编译器将使用0std::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";

如果您不想这样做,但您需要将该功能改为通用类型,然后使用SFINAE来约束answer提供的Barry之类的模板

答案 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_ifdecltype评估进行编译时验证:

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评估,以测试OutputInput类型。使用C ++ 17,您还可以在std::is_convertible_v<std::invoke_result_t<F&, Output, Input>,Output>中使用enable_if,如Barry中其他答案所示。