Determining which overload was selected

2019-01-24 03:39发布

问题:

Let's say I have some arbitrary complicated overloaded function:

template <class T> void foo(T&& );
template <class T> void foo(T* );
void foo(int );

I want to know, for a given expression, which foo() gets called. For example, given some macro WHICH_OVERLOAD:

using T = WHICH_OVERLOAD(foo, 0);       // T is void(*)(int);
using U = WHICH_OVERLOAD(foo, "hello"); // U is void(*)(const char*);
// etc.

I don't know where I would use such a thing - I'm just curious if it's possible.

回答1:

Barry, sorry for the misunderstanding in my first answer. In the beginning I understood your question in a wrong way. 'T.C.' is right, that it is not possible except in some rare cases when your functions have different result types depending on the given arguments. In such cases you can even get the pointers of the functions.

#include <string>
#include <vector>
#include <iostream>

//template <class T> T foo(T ) { std::cout << "template" << std::endl; return {}; };
std::string foo(std::string) { std::cout << "string" << std::endl; return {}; };
std::vector<int> foo(std::vector<int>) { std::cout << "vector<int>" << std::endl; return {}; };
char foo(char) { std::cout << "char" << std::endl; return {}; };

template<typename T>
struct Temp
{
    using type = T (*) (T);
};

#define GET_OVERLOAD(func,param) static_cast<Temp<decltype(foo(param))>::type>(func);

int main(void)
{
    auto fPtr1 = GET_OVERLOAD(foo, 0);
    fPtr1({});

    auto fPtr2 = GET_OVERLOAD(foo, std::string{"hello"});
    fPtr2({});

    auto fPtr3 = GET_OVERLOAD(foo, std::initializer_list<char>{});
    fPtr3({});

    auto fPtr4 = GET_OVERLOAD(foo, std::vector<int>{});
    fPtr4({});

    auto fPtr5 = GET_OVERLOAD(foo, std::initializer_list<int>{});
    fPtr5({});

    return 0;
}

The output is:

char
string
string
vector<int>
vector<int>


回答2:

I'm probably far from what you have in mind, but I've spent my time on that and it's worth to add an answer (maybe a completely wrong one, indeed):

#include<type_traits>
#include<utility>

template <class T> void foo(T&&);
template <class T> void foo(T*);
void foo(int);

template<int N>
struct choice: choice<N+1> { };

template<>
struct choice<3> { };

struct find {
    template<typename A>
    static constexpr
    auto which(A &&a) {
        return which(choice<0>{}, std::forward<A>(a));
    }

private:
    template<typename A>
    static constexpr
    auto which(choice<2>, A &&) {
        // do whatever you want
        // here you know what's the invoked function
        // it's template<typename T> void foo(T &&)
        // I'm returning its type to static_assert it
        return &static_cast<void(&)(A&&)>(foo);
    }

    template<typename A>
    static constexpr
    auto which(choice<1>, A *) {
        // do whatever you want
        // here you know what's the invoked function
        // it's template<typename T> void foo(T *)
        // I'm returning its type to static_assert it
        return &static_cast<void(&)(A*)>(foo);
    }

    template<typename A>
    static constexpr
    auto
    which(choice<0>, A a)
    -> std::enable_if_t<not std::is_same<decltype(&static_cast<void(&)(A)>(foo)), decltype(which(choice<1>{}, std::forward<A>(a)))>::value, decltype(&static_cast<void(&)(A)>(foo))>
    {
        // do whatever you want
        // here you know what's the invoked function
        // it's void foo(int)
        // I'm returning its type to static_assert it
        return &foo;
    }
};

int main() {
    float f = .42;
    static_assert(find::which(0) == &static_cast<void(&)(int)>(foo), "!");
    static_assert(find::which("hello") == &static_cast<void(&)(const char *)>(foo), "!");
    static_assert(find::which(f) == &static_cast<void(&)(float&)>(foo), "!");
    static_assert(find::which(.42) == &static_cast<void(&)(double&&)>(foo), "!");
}

I'll delete this answer after a short period during the which I expect experts to curse me. :-)