How I can specialize template when a method is available?

Viewed 84

I wonder how I can have a specialized template for when my type has a specific method. Let's take the following code as an example:

template<typename T, typename... Args>
void foo(T&& arg, Args&&... args) 
{
    std::cout << "called 1\n";
}

template<typename T, std::enable_if_t<std::is_member_function_pointer_v<decltype(&T::log)>, int> = 0>
void foo(T&& v)
{
    v.log();
}

struct Y
{
    void log() const 
    {
        std::cout << "called 2\n";
    }
};

int main()
{
    foo(1); // <-- print "called 1";
    Y y;
    foo(y); // <-- print "called 2";
}

I should say that this code does not work (2nd foo will use main template and prints called 1) and I know I cannot partially specialize a function call, but it shows what I need.

Any idea how I can achieve something like this? Should I use structures to partially specialize and then use helper functions?

2 Answers

You do not need to be that much verbose; You can enable the other foo, by as follows:

template<typename T>
auto foo(T&& v) -> decltype(v.log(), void())  
{
    v.log();
}

(See a Demo)

It is called the trailing return type. You provide the decltype to evaluate the expressions (1. v.log(), 2. void()) with comma operator separation. If the first expression(i.e. v.log()) fails, the overload is not the suitable one to call. Hence, it goes for the other foo. Otherwise, it accepts this overload with void return, which is evalued from the void() expression!

You are on the good track, but, with forwarding reference,

decltype(&T::log) becomes decltype(&(Y&)::log) which is invalid.

Using std::decay solves your issue:

template<typename T, 
    std::enable_if_t<
        std::is_member_function_pointer_v<decltype(&std::decay_t<T>::log)>,
        int> = 0>
void foo(T&& v)
{
    v.log();
}

Demo

Related