Overload Resolution with Ref-Qualifiers

Overload resolution with ref-qualifiers

Firstly, the implicit object parameter is treated as a normal parameter as per 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

where X is the class of which the function is a member and cv is the cv-qualification on the member
function declaration.

So what you are asking is equivalent to the following:

void bar(foo&);
void bar(foo&&);
void bar(const foo&);
void bar(const foo&&);

int main()
{
bar(foo());
}

The expression foo() is a class prvalue.

Secondly, the non-const lvalue reference version is not viable, as a prvalue cannot bind to it.

This leaves us with three viable functions for overload resolution.

Each has a single implicit object parameter (const foo&, foo&& or const foo&&), so we must rank these three to determine the best match.

In all three case it is a directly bound reference binding. This is described in declarators/initialization (8.5.3).

The ranking of the three possible bindings (const foo&, foo&& and const foo&&) is described in 13.3.3.2.3:

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if

  • S1 and S2 are reference bindings and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier [this exception doesn't apply here, they all have ref-qualifiers], and S1 binds an rvalue reference to an rvalue [a class prvalue is an rvalue] and S2 binds an lvalue reference.

This means that both foo&& and const foo&& are better then const foo&.

  • S1 and S2 are reference bindings, and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

This means that foo&& is better than const foo&&.

So Clang is right, and it is a bug in GCC. The overload ranking for foo().bar() is as follows:

struct foo
{
int&& bar() &&; // VIABLE - BEST (1)
int const&& bar() const &&; // VIABLE - (2)
int const& bar() const &; // VIABLE - WORST (3)
int& bar() &; // NOT VIABLE

int _bar;
};

The bug in GCC seems to apply purely to implicit object parameters (with ref-qualifiers), for a normal parameter it seems to get the ranking correct, at least in 4.7.2.

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

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

Overload resolution of template methods with ref-qualifiers

MSVC is wrong here.

The relevant rule is [over.load]/2.3:

Member function declarations with the same name and the same parameter-type-list as well as member function template declarations with the same name, the same parameter-type-list, and the same template parameter lists cannot be overloaded if any of them, but not all, have a ref-qualifier ([dcl.fct]).

Here the function templates have different template parameters (int I and class Q), so this rule does not apply, and there is no other rule stoping them from overloading.

call of overloaded with ref-qualifiers member function is ambiguous

g++ 4.8.2 has the same problem with the even simpler (Live at coliru):

struct A {
auto f() & {}
auto f() && {}
};

int main() {
A{}.f();
A a;
a.f();
}

despite the program obviously being correct. It appears to be a bug in the interaction between ref-qualifiers and return type deduction: presumably the deduction process is stripping the qualifiers from the implicit object argument before handing them off to overload resolution.

I have reported this as GCC bug 60943.

Why do ref-qualifier together with cv-qualifier on operator overloading allow rvalue assignment?

Because rvalues could be bound to lvalue-reference to const. Just same as the following code:

foo& r1 = foo();       // invalid; rvalues can't be bound to lvalue-reference to non-const
const foo& r2 = foo(); // fine; rvalues can be bound to lvalue-reference to const

BTW: The overload qualified with rvalue-reference wins in overload resolution when calling on rvalues. That's why you mark it as delete explicitly works as expected.

Overloading a parent member function without ref-qualifier with a child member function with ref-qualifier in C++

GCC is correct to accept this, but the situation changed recently. The current phrasing is that a using-declaration in a class ignores (base-class) declarations that would be ambiguous (in a sense that is more strict than for overload resolution, partly because there is no argument list yet) with other declarations in the class. void() and void() & members are ambiguous in this sense, so b.f finds only B’s f and the call is valid.

In previous (as of this writing, that means “published”) versions of the standard, both functions would be made available because the & distinguished them (in a sense that is even stricter), which would not only render the call ambiguous (as Clang says) but be ill-formed outright because the base- and derived-class functions were checked for overload compatibility which they lack.

rvalue ref-qualifiers for STL containers

There isn't a particular problem with adding ref-qualifiers to everything that returns references, however that basically doubles the number of members, which will generally have identical implementations apart from wrapping the return in std::move.

class A
{
int a;

int& operator[](std::size_t pos) & { return a; }
int&& operator[](std::size_t pos) && { return std::move(a); }
};

The standard library has declined to provide these overloads, in the same way that it has declined to provide many volatile overloads. In this case, you can just std::move the & value, where you need it.

If you are writing your own containers, then there is no safeness reason to avoid such overloads. It does increase the maintenance burden, so I would advise against it.



Related Topics



Leave a reply



Submit