为什么模板参数推导会因重载函数而失败?

Why does template argument deduction fail with overloaded function?

我有一个模板函数,它应该接受一个函数指针和参数,然后用给定的参数调用函数指针(我们称之为 Invoke)。但是,当我以重载函数作为参数调用模板函数时,模板推导失败。

我使用了 enable_if 以便只有一个重载有效,但这没有帮助。

#include <string>
#include <type_traits>

void foo(int, int){}
void foo(std::string, std::string) {}

template <bool Val1, bool Val2, bool ...Rest>
struct And
{
    enum {value = And<Val1 && Val2, Rest...>::value};
};
template <bool Val1, bool Val2>
struct And<Val1, Val2>
{
    enum {value = Val1 && Val2};
};

template <typename ...Params, typename ...Args, typename = typename std::enable_if<
    And<std::is_convertible<Args, Params>::value...>::value
>::type>
void Invoke(void (*fn)(Params...), Args ...args){}

int main() {
    Invoke(&foo, "a", "b");
    return 0;
}

试穿 ideone

当两个重载都存在时,编译器会抱怨 mismatched argument pack lengths while expanding ‘std::is_convertible<Args, Params>::value’。 当我注释掉 int 重载时,程序编译得很好,当我注释掉 std::string 重载时,推导失败,因为 const char[] 不能隐式转换为 int.

标准(C++17 标准的第 17.8.2.1.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.

所以我希望编译器会尝试 int 重载,推导会失败。当它尝试 std::string 重载时,推导会成功。

由于只有一个重载集成员的推导会成功,我希望它会像 int 重载不存在一样继续进行,并且编译会像以下情况一样成功另一个重载被注释掉了,但它失败了。

我哪里错了?

参考标准将不胜感激。

&foo不是函数指针,而是重载集。你必须明确:

Invoke(static_cast<void(*)(std::string, std::string)>(&foo), "a", "b");

为了简化失败 enable_if,您可以使用一个未指定的函数指针类型,使用可变参数包,并检查 is_invocablehttps://en.cppreference.com/w/cpp/types/is_invocable

这里的问题是有不止一个函数可以工作。 Params... 的扣除将在您到达模板的 SFINAE 部分之前发生。当它试图从 void (*fn)(Params...) 推导出 Params.. 时,它匹配 void foo(int, int)void foo(std::string, std::string)。由于它找到多个匹配项,17.8.2.1.6.2 声明它被视为非推导上下文。

由于它无法推断类型,因此您会遇到硬停止错误。 SFINAE 仅在模板参数推导步骤之后发生,在这种情况下无法到达。