3

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?

JeJo
  • 30,635
  • 6
  • 49
  • 88
Afshin
  • 8,839
  • 1
  • 18
  • 53

2 Answers2

3

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!

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Thanks. what is 2nd one?it is not specialization, is it? Why it is selected over main template? – Afshin Aug 11 '21 at 16:24
  • But why it is tested first at all? Because in case of my code, it is not checked at all. My 2nd template is valid, but it will not be checked till I remove main template. – Afshin Aug 11 '21 at 16:28
  • @Afshin For the function call `foo(1);` which one is suitable? The one with triadic args? or the single one? Single arg right? Therefore, it checks the single one first and the other. – JeJo Aug 11 '21 at 16:30
  • Aha, I got what you mean :) – Afshin Aug 11 '21 at 16:33
3

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

JeJo
  • 30,635
  • 6
  • 49
  • 88
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • ouch.....I could not see the problem, because when I remove main template, this one will be called even without decay. I wonder why is that. – Afshin Aug 11 '21 at 16:32