Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is overloading on just one ref-qualifier not allowed?

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.

like image 833
Lmn Avatar asked Feb 14 '16 08:02

Lmn


People also ask

Can we overload member functions?

Member function declarations with the same parameters or the same name types cannot be overloaded if any one of them is declared as a static member function.

Can we overload member functions in c++?

You can overload both member functions and free functions. The following table shows which parts of a function declaration C++ uses to differentiate between groups of functions with the same name in the same scope.

What is the point of function overloading?

The main advantage of function overloading is that it improves code readability and allows code reusability. The use of function overloading is to save memory space, consistency, and readability. It speeds up the execution of the program.


2 Answers

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 &&.

like image 182
M.M Avatar answered Nov 01 '22 14:11

M.M


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

like image 21
Niall Avatar answered Nov 01 '22 12:11

Niall