SFINAE 和重载函数的地址

SFINAE and the address of an overloaded function

我正在尝试在另一个函数参数 (foo1/foo2) 的上下文中解析重载函数 (bar) 的地址。

struct Baz {};

int bar() { return 0; }
float bar(int) { return 0.0f; }
void bar(Baz *) {}

void foo1(void (&)(Baz *)) {}

template <class T, class D>
auto foo2(D *d) -> void_t<decltype(d(std::declval<T*>()))> {}

int main() {
    foo1(bar);      // Works
    foo2<Baz>(bar); // Fails
}

foo1 没有问题,它明确指定了 bar 的类型。

但是,foo2,它通过 SFINAE 对除 bar 的一个版本之外的所有版本禁用自身,无法编译并显示以下消息:

main.cpp:19:5: fatal error: no matching function for call to 'foo2'
    foo2<Baz>(bar); // Fails
    ^~~~~~~~~
main.cpp:15:6: note: candidate template ignored: couldn't infer template argument 'D'
auto foo2(D *d) -> void_t<decltype(d(std::declval<T*>()))> {}
     ^
1 error generated.

据我了解,C++ 无法同时解析重载函数的地址和执行模板参数推导。

是这个原因吗?有没有办法让 foo2<Baz>(bar); (或类似的东西)编译?

这里是一些通用的答案:Expression SFINAE to overload on type of passed function pointer

对于实际情况,无需使用类型特征或 decltype() - 良好的旧重载解析将 select 最适合您的函数并将其分解为 'arguments'和 'return type'。枚举所有可能的调用约定

// Common functions
template <class T, typename R> void foo2(R(*)(T*)) {}

// Different calling conventions
#ifdef _W64
template <class T, typename R> void foo2(R(__vectorcall *)(T*)) {}
#else
template <class T, typename R> void foo2(R(__stdcall *)(T*)) {}
#endif

// Lambdas
template <class T, class D>
auto foo2(const D &d) -> void_t<decltype(d(std::declval<T*>()))> {}

将它们包装在模板结构中可能会很有用

template<typename... T>
struct Foo2 {
    // Common functions
    template <typename R> static void foo2(R(*)(T*...)) {}
    ...
};
Zoo2<Baz>::foo2(bar);

不过,成员函数需要更多代码,因为它们具有修饰符 (constvolatile&&)

如评论中所述,[14.8.2.1/6](工作草案,从函数调用中推导模板参数)在这种情况下的规则(强调我的):

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

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

  • 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.

SFINAE 会在推演结束后参与游戏,因此绕过标准规则无济于事。
有关更多详细信息,您可以在上面链接的项目符号末尾查看示例。

关于你的最后一个问题:

Is there a way to make foo2<Baz>(bar); (or something similar) compile ?

两种可能的选择:

  • 如果不想修改foo2的定义,可以调用为:

    foo2<Baz>(static_cast<void(*)(Baz *)>(bar));
    

    这样您就可以明确地从重载集中选择一个函数。

  • 如果允许修改foo2,可以改写为:

    template <class T, class R>
    auto foo2(R(*d)(T*)) {}
    

    它或多或少是你以前拥有的,在这种情况下没有 decltype 和一个你可以随意忽略的 return 类型。
    实际上你不需要使用任何 SFINAE 的函数来做到这一点,演绎就足够了。
    在这种情况下 foo2<Baz>(bar); 被正确解析。