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

时间:2014-12-17 10:42:08

标签: c++ templates c++11 function-pointers sfinae

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

// 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);

我们可以通过为foo()添加重载来破解此代码。

int foo(int i) { return 0; }

名称&#34; foo&#34;现在是模糊的,示例将不再编译。这可以通过显式提供函数指针类型信息来进行编译。

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

int (*func_takes_int)(int) = foo;
int b = call(func_takes_int, 0);

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);

这个想法在实际代码中得到了解决。

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);

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

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

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

相关问题

有一些问题询问有关函数指针类型的重载问题。看起来似乎无法做到这一点。我没有通过表达SFINAE找到任何问题。

这似乎是最相关的问题。

Is there a way to deduce the value of a function pointer template parameter?

奖励迂腐

&#34;功能指针&#34;在标题中使用的正确短语?会&#34;功能参考&#34;更精确?

2 个答案:

答案 0 :(得分:6)

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

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

并传递sfoo(或sfoo())而不是foo

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

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

答案 1 :(得分:2)

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

然而,在你的例子中,问题可能不是你有太多的“foo”重载,而是“call”的重载太少。只需提供两个模板的typename 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
}

通过添加返回类型推导,可以使调用重载更通用。在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);