1

The following code works and the overloads are found as expected:

struct HasBuzz
{
    void buzz() const {}
};

struct NoBuzz {};

template <typename T>
void foo(T const& t)
{
    t.buzz();
}

void foo(NoBuzz const&){}

int main()
{
    foo(HasBuzz{});
    foo(NoBuzz{});
}

However, if I replace the first overload with a "universal reference" version then it no longer works. The correct overload is not found for NoBuzz.

struct HasBuzz
{
    void buzz() const {}
};

struct NoBuzz {};

template <typename T>
void foo(T&& t)
{
    t.buzz();
}

void foo(NoBuzz const&){}

int main()
{
    foo(HasBuzz{});
    foo(NoBuzz{}); // error: NoBuzz has no member function buzz
}

What can I do to make it work?

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
isarandi
  • 3,120
  • 25
  • 35
  • 1
    Overload resolution has nothing to do with "the guts" of the function. – juanchopanza Jun 04 '14 at 16:44
  • @juanchopanza: Ah, yeah I misinterpreted the reason for the overload choice in the first case. But the question still remains. – isarandi Jun 04 '14 at 16:47
  • 1
    `void foo(T&& t)` is a better match than binding the temporary to a const reference `void foo(NoBuzz const&)`, so the compiler will use the rvalue reference function. I had kind of a similar question, see http://stackoverflow.com/questions/24029213/overloading-reference-vs-const-reference – vsoftco Jun 04 '14 at 16:48
  • I think the answer to your underlying question is `std::enable_if` ( http://en.cppreference.com/w/cpp/types/enable_if ). – Max Lybbert Jun 04 '14 at 16:52

2 Answers2

9

Simple solution

Add an overload that is callable with an rvalue of type NoBuzz.

void foo(NoBuzz const&){ }; 
void foo(NoBuzz&&)     { }; // overload for rvalues


Note: Depending on your actual use case this might not be enough, because if you pass a non-const lvalue type NoBuzz to foo you'd still instantiate the template, since the two NoBuzz overloads doesn't match.

At the end of this post is a more complex, but certainly cleaner, solution.


Explanation

 template<class T>
 void foo (T&&);            // (A)
 void foo (NoBuzz const&);  // (B)

The problem with your snippet is that your template (A) can be instantiated in such a way that it's a better match than your overload (B).


When the compiler sees that you are trying to call a function named foo with an argument which is an rvalue of type NoBuzz, it will look for all functions named foo taking one argument where a NoBuzz would fit.

Let's say it starts of with your template (A), here it sees that T&& is deducable to any reference type (both lvalue, and rvalue), since we are passing an rvalue T = NoBuzz.

With T = NoBuzz the instantiated template would be semantically equivalent to:

void foo (NoBuzz&&); // (C), instantiated overload of template (A)


It will then continue to your overload (B). This overload accepts a const lvalue reference, which can bind to both lvalues and rvalues; but our previous template instantiation (C) can only bind to rvalues.

Since (C) is a better match than (B), binding rvalues to T&& is prefered over U const&, that overload is selected and you get the behavior you describe in your post.


Advanced solution

We can use a technique called SFINAE to conditionally make it impossible to call the template if the type passed doesn't implement .buzz ().

template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
  return t.buzz();
}

The above solution uses a lot of new features of C++11, detailed information is available here:

Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
2

refp's answer explains the behavior you're seeing, and offers a possible solution. Another option is to ensure the foo function template does not make it into the candidate set for overload resolution unless T has a member function named buzz().

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

After making this change the foo(NoBuzz const&) overload will be selected when you pass it an instance of NoBuzz. Live demo

A detailed explanation of what's going on in the decltype expression in the trailing return type can be found here. The only thing I've done differently here is instead of using three subexpressions, with the middle one being void() to prevent a user defined operator, from being selected, I've cast the result of the first expression to void; the intent and result are identical in both cases.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 1
    *"instead of using a second subexpression `void()`"* Well you *do* use a `, void()` -- isn't that redundant with the cast? – dyp Jun 04 '14 at 18:18
  • @dyp :) That *second subexpression* refers to the explanation in the linked answer. I guess you are right about the redundancy in this case since the return type is also `void`, but I'm going to leave that in there so that the linked explanation still makes sense. – Praetorian Jun 04 '14 at 18:25