表达式SFINAE在传递的函数指针类型上重载

Pra*_*tic 6 c++ templates function-pointers sfinae c++11

在此示例中,函数被传递给隐式实例化的函数模板.

// Function that will be passed as argument
int foo() { return 0; }

// Function template to call passed function
template<typename F>
int call(F f) {
    return f();
}

template<typename F, typename A>
int call(F f, A a) {
    return f(a);
}

int a = call(foo);
Run Code Online (Sandbox Code Playgroud)

我们可以通过添加重载来破坏此代码foo().

int foo(int i) { return 0; }
Run Code Online (Sandbox Code Playgroud)

名称" foo"现在不明确,示例将不再编译.这可以通过显式提供函数指针类型信息来进行编译.

int (*func_takes_void)() = foo;
int a = call(func_takes_void);

int (*func_takes_int)(int) = foo;
int b = call(func_takes_int, 0);
Run Code Online (Sandbox Code Playgroud)

http://coliru.stacked-crooked.com/a/e08caf6a0ac1e6b9

是否可以推断出函数指针类型?如果是这样,为什么我的尝试下面不起作用,这是正确的方法?

如果这是不可能的,一个好的答案可以解释原因.

到目前为止尝试

foo()通过检查定义,人类可以看到两个调用中的目标,call<>()但编译器无法获得重载解析信息.尽管如此,信息仍然存在,只需将其拉入功能模板签名即可.这可能是表达SFINAE的可能.

在伪代码中我们想要这个:

template<IgnoreThis, typename ReturnType>
struct expr_check
{
    typedef ReturnType type;
}

template<typename F>
expr_check<expression requiring F have correct signature, result_of<F>::type>::type
call(F f);
Run Code Online (Sandbox Code Playgroud)

这个想法是在真实的代码中解决的.

http://coliru.stacked-crooked.com/a/a3ce828d6cb16c2d

功能模板签名是:

template<typename F>
typename expr_check<sizeof(declval<F>()()), typename func_ptr_result<F>::type>::type
call(F f);

template<typename F, typename A>
typename expr_check<sizeof(declval<F>()(declval<A>())), typename func_ptr_result<F>::type>::type
call(F f, A a);
Run Code Online (Sandbox Code Playgroud)

我目前没有编译.从编译器输出中可以看出,在两次尝试实例化函数模板时,一个call<>()重载上存在替换失败,另一个只是给出了一个不透明的"无法推断模板参数".

(colirus编译为C++ 03,但C++ 11答案很好.)

我怀疑是在实例化时call<>(),foo()没有被调用,C++根本就没有提供foo()此上下文中的重载解析.可以证明一个foo()重载是正确的,C++并不强制要求重载解析.另一方面,重载决策不限于被调用的函数.适当类型的函数指针可以选择重载foo().

相关问题

有几个问题询问有关函数指针类型的重载.看起来这是无法做到的.通过表达SFINAE,我没有发现任何试图这样做的问题.

这似乎是最相关的问题.

有没有办法推断出函数指针模板参数的值?

奖金迂腐

"函数指针"是否在标题中使用了正确的短语?"功能参考"会更准确吗?

n. *_* m. 6

你可以得到的最接近的可能是:

struct sfoo
{
  template<typename... args>
  void operator() (args&&... a)
  { 
    foo(std::forward<args>(a)...);
  }
};
Run Code Online (Sandbox Code Playgroud)

并传递sfoo(或sfoo())而不是foo周围.

也就是说,创建一个函数对象类型,它封装了模板化中的整个重载集operator().

然后,不是对不存在的模板参数进行重载解析,而是通过相同的参数获得模板实例化,这是正常的.


Arn*_*gel 2

正如之前提到的,SFINAE 不起作用,因为重载函数的名称在 C++ 中没有明确的类型,因此模板参数替换在这个阶段甚至不会发生。

但是,在您的示例中,问题可以说不是“foo”的重载太多,而是“call”的重载太少。只需提供类型名为 F 的模板需要函数指针的模板即可。编译器现在将能够根据上下文执行正确的操作:

#include <iostream>

// Functions
int foo() { return 0; }

int foo(int) { return 1; }

// Function object
struct Foo
{
    int operator()() const { return 2; }
    int operator()(int) const { return 3; }
};

// Higher-order functions / templates
template<typename F>
int call(F f) {
    return f();
}

int call(int (*f)()) {
    return f();
}

template<typename F, typename A>
int call(F f, A a) {
    return f(a);
}

template<typename A>
int call(int (*f)(A), A a) {
    return f(a);
}

int main()
{
    int a = call(foo)
      , b = call(foo, 0)
      , c = call(Foo())
      , d = call(Foo(), 0);
    std::cout << a << ',' << b << ',' << c << ',' << d << '\n';  // 0,1,2,3
}
Run Code Online (Sandbox Code Playgroud)

通过添加返回类型推导可以使调用重载更加通用。在 C++11 中,即使对于函数对象,也可以通过使用 decltype rsp 实现这一点。的结果。为了简洁起见,我将仅发布新的函数签名,因为在这种情况下不需要更改主体:

template<typename F>
auto call(F f) -> decltype(f());

template<typename R>
R call(R (*f)());

template<typename F, typename A>
auto call(F f, A a) -> decltype(f(a));

template<typename R, typename A>
R call(R (*f)(A), A a);
Run Code Online (Sandbox Code Playgroud)