模板参数扣除失败

Template argument deduction failure

本文关键字:失败 参数      更新时间:2023-10-16

即使在以下程序中对fold_left的调用似乎足以推断模板函数的参数,但由于模板参数扣除失败而引起的汇编误差。

#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定义中参数的顺序与不相关。

为什么在此处进行模板参数扣除?

,因为std::plus<int>对于某些类型的O,I不是std::function<O(O,I)>的实例,并且它们处于推论的上下文中。

解决方案是用一个新的模板参数替换std::function<O(O,I)>,该参数本身只是根据两个参数O,I不可构成的要求,并且返回类型可转换为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参数,但实际上您实际上不需要std::function提供的类型删除,因此它是不确定的。

问题是std::function<Output(Output,Input)>无法正确推断其模板参数,因为std::plus<int>不是std::function

类型 - 否决仅在模板类型可以与所传递的参数匹配时有效;它不会执行通过转换/构造进行的转换;而是将类型全部视为。

在这种情况下, std::plus<int>不是 a 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,您也可以在enable_if中使用std::is_convertible_v<std::invoke_result_t<F&, Output, Input>,Output>,如Barry在其他答案中所建议的。

这对我来说很奇怪,因为Output模板参数应该很明显,因为我们提供了int作为第一个参数

编译器将尝试解决Output的每个位置。如果来自不同参数的推论类型不匹配,那么您将获得歧义,这将导致扣除次数失败。

由于您在Output zero中使用Output,并且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来约束模板,例如Barry提供的答案。