Expression SFINAE to overload on type of passed fu

2019-02-22 02:27发布

问题:

In this example a function is passed to an implicitly instantiated function template.

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

We can break this code by adding an overload for foo().

int foo(int i) { return 0; }

The name "foo" is now ambiguous and the example will no longer compile. This can be made to compile by explicitly providing function pointer type info.

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

Is it possible to instead deduce the function pointer types? If so, why does my attempt below not work and what is the right way to do this?

If this is not possible, a good answer would explain why.

Attempt thus far

A human can see which foo() is intended in the two calls by inspecting the definitions of call<>() but that info is not available to the compiler for overload resolution. Still, the information is all there, it just needs to be pulled into the function template signature. This may be possible with expression SFINAE.

In pseudo code we want this:

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

Here is that idea worked out in real code.

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

The function template signatures are:

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

What I currently have does not compile. From the compiler output you can see that on both attempts to instantiate the function template there is substitution failure on one call<>() overload and the other simply gives an opaque "couldn't deduce template parameter".

(The colirus were compiled as C++03 but C++11 answers are fine.)

My suspicion is that while instantiating call<>(), foo() is not being called and C++ simply does not provide for overload resolution of foo() in this context. It doesn't matter that it can be proven that one foo() overload is the correct one, C++ just doesn't mandate overload resolution here. On the other hand, overload resolution isn't limited to a function being called. A function pointer of appropriate type gets to select overloads of foo().

Related questions

There are a few questions asking about overloading on function pointer type. It looks like this can't be done. I didn't find any questions trying to do this through expression SFINAE.

This seems to be the closest related question.

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

Bonus pedantry

Is "function pointer" the correct phrase to have used in the title? Would "function reference" have been more accurate?

回答1:

As has been mentioned before, SFINAE doesn't work because the names of overloaded functions have no definite type in C++, therefore template parameter substitution doesn't even happen at this stage.

However, in your example, the problem is arguably not that you have too many overloads of "foo", but too few overloads of "call". Just provide both the templates with typename F and the ones that expect a function pointer. The compiler will now be able to do the right thing depending on context:

#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
}

The call overloads can be made more generic by adding return type deduction. In C++11, this is possible even with function objects by using decltype rsp. result_of. For brevity, I will post only the new function signatures, as the bodies don't need to be changed in this case:

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


回答2:

The closest you can get is probably this:

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

and pass sfoo (or sfoo()) instead of foo around.

That is, create a function object type that encapsulates the entire overload set in the templatized operator().

Then instead of overload resolution over a template argument, which does not exist, you get a template instantiation over the same argument, which is OK.