Search code examples
c++sfinaeoverloadingc++17template-argument-deduction

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 ?


Solution

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