Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE and the address of an overloaded function

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 ?

like image 545
Quentin Avatar asked Nov 10 '15 07:11

Quentin


2 Answers

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.

like image 61
skypjack Avatar answered Nov 05 '22 11:11

skypjack


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, &&)

like image 23
Mykola Bogdiuk Avatar answered Nov 05 '22 11:11

Mykola Bogdiuk