无法推断出作为函数的模板参数

Cannot deduce template argument that is a function

为什么 F 不能推导出 proxy()

这应该是可能的,因为我正在限制它 - 仅适用于 return 和 int.

的功能
#include <utility>
#include <iostream>
#include <type_traits>
using namespace std;

int foo(int bar) {
    cout << "int" << endl;
    return 2;
}

float foo(float bar) {
    cout << "float" << endl;
    return 1;
}

template <typename F, typename... Args>
typename enable_if<
    is_same<
        typename result_of<F(Args...)>::type,
        int
        >::value,
    typename result_of<F(Args...)>::type
    >::type
proxy(F func, Args&&... args) {
    return func(forward<Args>(args)...);
}

int main() {
    proxy(foo, 5);
}

这里是错误:

b.cpp:29:17: error: no matching function for call to 'proxy(<unresolved overloaded function type>, int)'
b.cpp:24:1: note:   template argument deduction/substitution failed:
b.cpp:29:17: note:   couldn't deduce template parameter 'F'

问题是这样的:

proxy(foo, 5);

编译器尝试推断 foo 的类型,但有 2 个重载。当然,它可以从 5 推导出 Args...,但是 foo 的类型仍然是不可推导的,因为编译器在进行类型推导时不知道选择哪个重载。

请注意,编译器需要知道函数签名中 F 的类型,即此处,因此 SFINAE 发挥了它的魔力:

is_same<
    typename result_of<F(Args...)>::type,
    int
>::value,

它绝对没有办法从 proxy(foo, 5) 调用中正确推断出 F 的类型,因此 SFINAE 无法启动。作为旁注,请注意 C++ 不能重载基于仅限 return 类型。因此,您将无法仅根据 return 类型来区分两个具有相同名称的函数。您需要以某种方式强制进行参数匹配,这将使 SFINAE 排除非候选重载。

某种程度上相关:

以及标准中的相关引用,强调我的(感谢@T.C。指出):

14.8.2.1 从函数调用中推导模板参数 [temp.deduct.call]/(6.2)

(6) When P is a function type, pointer to function type, or pointer to member function type:

  • (6.1) If the argument is an overload set containing one or more function templates, the parameter is treated as a non-deduced context.

  • (6.2) If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. If deduction succeeds for more than one member of the overload set the parameter is treated as a non-deduced context.

在您的示例中 foo 命名了一组重载函数,并且模板参数推导无法 select 一个重载另一个,因为它们都是同等级别的匹配项。

直到推导出 F 之后,您的 SFINAE 约束检查才会开始,因此从重载决议集中删除 float foo(float) 没有任何帮助。

假设您将返回 float 的函数重命名为 foof,那么您的示例将会编译。但是,如果您尝试使用 foof 作为函数参数调用 proxy,代码将再次编译失败,这次是因为 enable_if 约束。

要让您的示例在其当前状态下编译,您必须消除传递给 proxy

foo 的歧义
proxy(static_cast<int(*)(int)>(foo), 5);