12

I'm experimenting with resolving the address of an overloaded function (bar) in the context of another function's parameter (foo1/foo2).

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
}

There's no trouble with foo1, which specifies bar's type explicitly.

However, foo2, which disable itself via SFINAE for all but one version of bar, fails to compile with the following message :

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.

It is my understanding that C++ cannot resolve the overloaded function's address and perform template argument deduction at the same time.

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

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • 2
    The compiler only compares `bar` against `D*` for filtering out the other overloads of `bar`, as a sub-process of deciding what function to compare against `D*` later in the actual deduction process. It will not try to substitute the results of this trail sub-process into the remaining parts of the function because that only happens in the actual deduction. – Johannes Schaub - litb Nov 10 '15 at 07:42
  • So the question is "How to deduct the type of specific function overload?" or a simple `template void foo2(void(*)(T *)) {}` is enough? – Mykola Bohdiuk Nov 11 '15 at 07:39
  • @MykolaBogdiuk The practical case is to support any function that can be called with a `T*` (whatever its return type, for example), but the general answer interests me now. – Quentin Nov 11 '15 at 09:08
  • [temp.deduct.call]/p6 – T.C. Nov 12 '15 at 09:07
  • You might find the section "Selecting the operator()" interesting from [n3886](http://open-std.org/JTC1/SC22/WG21/docs/papers/2014/n3866.html) – Barrett Adair May 27 '16 at 21:22

2 Answers2

2

As mentioned in the comments, [14.8.2.1/6] (working draft, deducing template arguments from a function call) rules in this case (emphasis mine):

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 takes its part to the game once the deduction is over, so it doesn't help to work around the standard's rules.
For further details, you can see the examples at the end of the bullet linked above.

About your last question:

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

Two possible alternatives:

  • If you don't want to modify the definition of foo2, you can invoke it as:

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

    This way you explicitly pick a function out of the overload set.

  • If modifying foo2 is allowed, you can rewrite it as:

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

    It's more or less what you had before, no decltype in this case and a return type you can freely ignore.
    Actually you don't need to use any SFINAE'd function to do that, deduction is enough.
    In this case foo2<Baz>(bar); is correctly resolved.

skypjack
  • 49,335
  • 19
  • 95
  • 187
1

Some kind of the general answer is here: Expression SFINAE to overload on type of passed function pointer

For the practical case, there's no need to use type traits or decltype() - the good old overload resolution will select the most appropriate function for you and break it into 'arguments' and 'return type'. Just enumerate all possible calling conventions

// 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*>()))> {}

It could be useful to wrap them in a templated structure

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

Although, it will require more code for member functions as they have modifiers (const, volatile, &&)

Community
  • 1
  • 1
Mykola Bohdiuk
  • 1,307
  • 12
  • 16