SFINAE和重载函数的地址

SFINAE and the address of an overloaded function

本文关键字:地址 函数 重载 SFINAE      更新时间:2023-10-16

我正在尝试在另一个函数的参数(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的类型。

然而,除了一个版本的bar之外,foo2通过SFINAE对其自身禁用,无法编译,并显示以下消息:

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);(或类似的东西)?

正如评论中所提到的,[14-1.8.1/6](工作草案,从函数调用中推导模板参数)在这种情况下的规则(emphasis mine):

当p是函数类型、函数指针类型或指向成员函数类型的指针时:

  • 如果参数是包含一个或多个函数模板的重载集,则该参数将被视为非推导上下文。

  • 如果参数是重载集(不包含函数模板),则尝试使用该集的每个成员进行试参数推导。如果只有一个重载集成员的推导成功,则该成员将用作推导的参数值如果重载集的多个成员的推导成功,则参数将被视为非推导上下文

一旦扣除结束,SFINAE就会参与游戏,因此绕过标准规则也无济于事
有关进一步的详细信息,您可以看到上面链接的项目符号末尾的示例。

关于你的最后一个问题:

有没有办法编译foo2<Baz>(bar);(或类似的东西)?

两种可能的替代方案:

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

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

    通过这种方式,您可以显式地从重载集中选择一个函数。

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

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

    它或多或少是以前的,在这种情况下没有decltype,并且是一个可以自由忽略的返回类型
    实际上,你不需要使用任何SFINAE函数来实现这一点,推导就足够了
    在这种情况下,foo2<Baz>(bar);被正确地解析。

这里有一些常见的答案:表达式SFINAE对传递的函数指针的类型进行重载

对于实际情况,不需要使用类型traits或decltype()——好的旧重载解析会为您选择最合适的函数,并将其分解为"arguments"answers"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&&

,因此需要更多的代码