15

Apparently, overloading on ref-qualifiers is not allowed – this code won't compile if you remove either & or && (just the tokens, not their functions):

#include <iostream>

struct S {
    void f() &  { std::cout << "Lvalue" << std::endl; }
    void f() && { std::cout << "Rvalue" << std::endl; }
};

int main()
{
    S s;
    s.f();   // prints "Lvalue"
    S().f(); // prints "Rvalue"
}

In other words, if you have two functions of the same name and type, you have to define both if you define either. I assume this is deliberate, but what's the reason? Why not allow, say, calling the && version for rvalues if it's defined, and the "primary" f() on everything else in the following variation (and vice-versa – although that would be confusing):

struct S {
    void f()    { std::cout << "Lvalue" << std::endl; }
    void f() && { std::cout << "Rvalue" << std::endl; }
};

In other words, let them act similar to template specializations with respect to the primary template.

Leo Heinsaar
  • 3,887
  • 3
  • 15
  • 35
  • 4
    Changing what `void f() {...}` means depending on other overloads sounds confusing. – chris Feb 14 '16 at 08:46
  • Never seen the `&`/`&&` qualifier used in practice. – Lingxi Feb 14 '16 at 09:13
  • 2
    @Lingxi, One use is to turn traps into compiler errors. For example, people might mistakenly do `const char* str = getStr().c_str();`. You could use this to turn that into a compiler error. Unfortunately, there's no current way to make it foolproof (i.e., be an error there, but still work in `foo(getStr().c_str());`). I know it's being looked into on std-proposals. Also see [this question](http://stackoverflow.com/questions/21052377/whats-a-use-case-for-overloading-member-functions-on-reference-qualifiers) for uses. – chris Feb 14 '16 at 09:16
  • There seems to be no question on StackOverflow on a good use of ref-qualifiers. – Leo Heinsaar Feb 14 '16 at 09:25
  • 2
    You don't have to define both. It's fine to have just one., e.g. `void f() &&` without any other definitions of `f` – M.M Feb 14 '16 at 10:21
  • 2
    Because of [over.match.funcs] 13.3.1\5.3, a member function without a _ref-qualifier_ acts like a double-barreled shotgun on _lvalue_ s and _rvalue_ s arguments. So when you declare a member function **with** any of twin-sister _ref-qulifier_ s, that shotgun must be removed, because of the ambiguity being introduced. – Eugene Zavidovsky Feb 14 '16 at 19:00

3 Answers3

14

It's not any different to the following situation:

struct S {};

void g(S s);
void g(S& s);

int main()
{
    S s;
    g(s);     // ambiguous
}

Overload resolution has always worked this way; passing by reference is not preferred to passing by value (or vice versa).

(Overload resolution for ref-qualified functions works as if it were a normal function with an implicit first parameter whose argument is *this; lvalue-ref qualified is like a first parameter S &, const & is like S const & etc.)

I guess you are saying that g(s) should call g(S&) instead of being ambiguous.

I don't know the exact rationale, but overload resolution is complicated enough as it is without adding more special cases (especially ones that may silently compile to not what the coder intended).

As you note in your question, the problem can be easily avoided by using the two versions S & and S &&.

M.M
  • 138,810
  • 21
  • 208
  • 365
4

You can have both, or either. There is no specific requirement to include both if you implement the one.

The catch is that a member method (non-static member) that is not marked with a qualifier, is suitable for use with both lvalues and rvalues. Once you overload a method with a ref-qualifier, unless you mark the others as well, you run into ambiguity issues.

During overload resolution, non-static cv-qualified member function of class X is treated as a function that takes an implicit parameter of type lvalue reference to cv-qualified X if it has no ref-qualifiers or if it has the lvalue ref-qualifier. Otherwise (if it has rvalue ref-qualifier), it is treated as a function taking an implicit parameter of type rvalue reference to cv-qualified X.

So basically, if you have one method that is qualified (e.g. for an lvalue &) and one that is not qualified, the rules are such that they are effectively both qualified and hence ambiguous.

Similar rationale is applied to the const qualifier. You can implement a method and have one "version" for a const object, and one for a non-const object. The standard library containers are good examples of this, in particular the begin(), end() and other iterator related methods.

One particular use case is when the logic applied to the method is different between when the object is a temporary (or expiring) object and when it is not. You may wish to optimise away certain calls and data processing internally if you know the lifetime is about to end.

Another is to limit the use of a method to lvalues. A certain piece of application or object logic may not make sense or be useful if the entity is about to expire or is a temporary.


The wording in the standard (taken from the draft N4567) from §13.4.1/4 is:

For non-static member functions, the type of the implicit object parameter is

  • “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier

  • “rvalue reference to cv X” for functions declared with the && ref-qualifier

Niall
  • 30,036
  • 10
  • 99
  • 142
  • Good explanation. But why exactly is there ambiguity in the example? – Yam Marcovic Feb 15 '16 at 08:10
  • 1
    @YamMarcovic. The non-qualified member are implicitly qualified appropriately for the argument, hence, in the presence of an explicit qualified member, they are "equivalent"... I've added a quote from the cppreference site. – Niall Feb 15 '16 at 08:43
  • @YamMarcovic For an answer to that question, you may be interested in 13.3.3.2\3.2.3; 13.3.3\1. – Eugene Zavidovsky Feb 15 '16 at 12:55
  • 1
    @EugeneZavidovsky :) Right after commenting I opened the standard and explored. Hence, my answer. – Yam Marcovic Feb 15 '16 at 13:02
1

Let's start with what it means to define a basic non-static member function without any ref qualifiers.

§13.3.1 [4]

For non-static member functions, the type of the implicit object parameter is

— "lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier

— "rvalue reference to cv X” for functions declared with the && ref-qualifier

But wait, there's more.

[5] For non-static member functions declared without a ref-qualifier, an additional rule applies:

even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.

Therefore

  1. You can overload for just one ref type: lvalue, rvalue
  2. You can't overload one or the other and then also add in another that's not ref qualified, because the one that's not ref qualified is defined to bind both types, and hence the ambiguity.
Yam Marcovic
  • 7,953
  • 1
  • 28
  • 38