简体   繁体   English

模板参数推导失败

[英]Template argument deduction failure

Even though it seems the call to fold_left in the following program is specific enough to deduce the templated function's argument, a compilation error arises due to a template argument deduction failure. 即使在以下程序中对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"; ^ 

This minimal program would only compile if fold_left is called with: 仅当使用以下fold_left调用fold_left此最小程序才会编译:

fold_left<int, int>(0, std::plus<int>{}, data);

This feels strange to me since the Output template argument should be obvious since we provided an int as the first argument ( zero ); 这对我来说很奇怪,因为Output模板参数应该很明显,因为我们将int用作第一个参数( zero ); similarly the Input template argument should be deduced correctly since we have provided an std::valarray<int> as the third argument ( data ) expecting a std::valarray<Input> . 类似地,应该正确推导Input模板参数,因为我们提供了std::valarray<int>作为第三个参数( data ),期望std::valarray<Input>

The order of the arguments in the definition of fold_left are not relevant . 在定义参数的顺序fold_left 是不相关的

Why does template argument deduction fail here? 为什么模板参数推导在这里失败?

Because std::plus<int> is not an instance of std::function<O(O,I)> for some types O,I and they are in a deduced context there. 因为对于某些类型的O,I std::plus<int>不是std::function<O(O,I)>的实例O,I并且它们在推断的上下文中存在。

The solution is to replace std::function<O(O,I)> with a new template parameter that is itself just constrained based on the requirement that it is invocable with two arguments O,I and that the return type is convertible to O : 解决方案是将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)
{ ... }

You could separately wrap the op argument in a non-deduced context, but you don't actually need the type erasure that std::function provides, so it's unnecssary. 您可以将op参数单独包装在一个非推论上下文中,但是实际上不需要std::function提供的类型擦除,因此它是不必要的。

This feels strange to me since the Output template argument should be obvious since we provided an int as the first argument 这对我来说很奇怪,因为Output模板参数应该很明显,因为我们将int作为第一个参数

The compiler will try to resolve Output is in every location it is used. 编译器将尝试解析在每个使用Output位置。 If the deduced types from different parameters don't match then you'll get an ambiguity which will cause the deduction to fail. 如果从不同参数推断出的类型不匹配,那么您将得到歧义,这将导致推断失败。

Since you use Output in Output zero and std::function<Output(Output,Input)> op the compiler is going to use 0 and std::plus<int> to try and figure out what Output is. 由于您使用OutputOutput zerostd::function<Output(Output,Input)> op编译器将使用0std::plus<int>尝试,并找出Output的。 std::plus<int> is not a std::function` so it can't figure out the type and that is why the substitution fails. std::plus<int>不是std :: function`,因此它无法弄清类型,这就是替换失败的原因。

If you cast std::plus<int>{} into a std::function then the compiler can deduce the template types 如果将std::plus<int>{}转换为std::function则编译器可以推断出模板类型

std::cout << fold_left(0, std::function<int(int, int)>(std::plus<int>{}), data) << "\n";

If you don't want to have to do this though you will need to instead make the function a generic type and then use SFINAE to constrain the template like answer provided by Barry does. 如果您不想这样做,则需要将函数设为泛型,然后使用SFINAE约束模板,就像Barry提供的答案一样。

The issue is that std::function<Output(Output,Input)> cannot properly deduce its template arguments, because std::plus<int> is not a std::function . 问题在于std::function<Output(Output,Input)>无法正确推断其模板参数,因为std::plus<int>不是std::function

Type-deduction only works if the template types can match up with the arguments being passed; 类型推断仅在模板类型可以与传递的参数匹配时才有效; it doesn't perform transformations that would be done through conversions/constructions; 它不会执行通过转换/构造进行的转换; but rather takes the type in totality. 而是采用整体类型。

In this case, std::plus<int> is not a std::function<int(int,int)> , it is convertible to a std::function<int(int,int)> . 在这种情况下, std::plus<int> 不是 std::function<int(int,int)> ,它可以转换std::function<int(int,int)> This is what is causing the deduction to fail. 这就是导致推论失败的原因。

The alternatives are to either break contributions to the type-deduction with an alias, to explicitly pass a std::function type, or to accept the function as a different template argument (which may actually be more efficient) 替代方法是使用别名破坏对类型推导的贡献,以显式传递std::function类型,或将函数接受为其他模板参数(实际上可能更有效)

1. Break contribution to the type-deduction with an alias 1.用别名打破对类型推导的贡献

If you make an alias of the type, so that the types don't contribute to the deduction, then it can leave the first and third arguments to determine the types of the std::function 如果为该类型创建别名,以使类型不构成推论,则可以保留第一个和第三个参数来确定std::function的类型。

(off the top of my head; untested) (在我的头顶;未经测试)

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)
{
    ....
}

It's a bit of an ugly approach, but it will disable std::function from contributing to the deduction because the types are transformed through nodeduce_t . 这有点丑陋,但由于类型是通过nodeduce_t转换的,因此nodeduce_t std::function进行nodeduce_t

2. Pass as std::function 2.作为std::function传递

This is the simplest approach; 这是最简单的方法。 your calling code would become: 您的调用代码将变为:

 fold_left(0, std::function<int(int,int)>(std::plus<int>{}), data);

Admittedly, it's not pretty -- but it would resolve the deduction because std::function<int(int,int)> can directly match the input. 诚然,它不是很漂亮-但是它将解决这个问题,因为std::function<int(int,int)>可以直接匹配输入。

3. Pass as template argument 3.作为模板参数传递

This actually has some additional performance benefits; 实际上,这还具有一些其他的性能优势。 if you accept the argument as a template type, then it also allows your function to be inlined better by the compiler (something that std::function has issues with) 如果您接受参数作为模板类型,那么它还允许编译器更好地内联函数( 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;
}

All of these are possible alternative that would accomplish the same task. 所有这些都是可以完成相同任务的替代方案。 It's also worth mentioning that compile-time validation can be done using SFINAE with things like enable_if or decltype evaluations: 还值得一提的是,可以使用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( ... ){ ... }

The decltype(...) syntax ensures the expression is well-formed, and if it is, then it allows this function to be used for overload resolution. decltype(...)语法可确保表达式格式正确,如果正确,则允许将此函数用于重载解析。

Edit : Updated the decltype evaluation to test Output and Input types. 编辑 :更新了decltype评估以测试OutputInput类型。 With C++17, you can also use std::is_convertible_v<std::invoke_result_t<F&, Output, Input>,Output> in an enable_if , as suggested by Barry in a different answer. 对于C ++ 17,您还可以在enable_if使用std::is_convertible_v<std::invoke_result_t<F&, Output, Input>,Output> ,这是Barry在其他答案中建议的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM