Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload resolution with ref-qualifiers

Tags:

While working with ref-qualified function overloads, I'm getting different results from GCC (4.8.1) and Clang (2.9 and trunk). Consider the following code:

#include <iostream> #include <utility>  struct foo {     int& bar() &     {         std::cout << "non-const lvalue" << std::endl;         return _bar;     }     //~ int&& bar() &&     //~ {     //~     std::cout << "non-const rvalue" << std::endl;     //~     return std::move(_bar);     //~ }     int const& bar() const &     {         std::cout << "const lvalue" << std::endl;         return _bar;     }     int const&& bar() const &&     {         std::cout << "const rvalue" << std::endl;         return std::move(_bar);     }      int _bar; };  int main(int argc, char** argv) {     foo().bar(); } 

Clang compiles it and outputs "const rvalue", while GCC thinks this is an ambiguous call with the two const-qualified functions both being best viable candidates. If I provide all 4 overloads, then both compilers output "non-const rvalue".

I would like to know which compiler --if any-- is doing the right thing, and what are the relevant standard pieces in play.

Note: The reason this actually matters is that the real code declares both const-qualified functions as constexpr. Of course, there is no output to std::cout and static_cast is used instead of std::move, so that they are valid constexpr definitions. And since in C++11 constexpr still implies const, the overload commented out in the sample code cannot be provided as it would redefine the const-qualified rvalue overload.

like image 910
K-ballo Avatar asked Jun 16 '13 05:06

K-ballo


People also ask

What is ref qualifier?

Ref-qualifiers are used to choose between normal and rvalue reference semantics, allowing the compiler to use either copy or move semantics depending on which are more appropriate, and are applied to *this instead of this . Note that despite ref-qualifiers using reference syntax, this itself is still a pointer.

What is overload resolution?

The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.

How do you create an overloaded function in C++?

You overload a function name f by declaring more than one function with the name f in the same scope. The declarations of f must differ from each other by the types and/or the number of arguments in the argument list.

What is function overloading in C++ with example?

Function Overloading in C++When a function name is overloaded with different jobs it is called Function Overloading. In Function Overloading “Function” name should be the same and the arguments should be different. Function overloading can be considered as an example of a polymorphism feature in C++.


1 Answers

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.

like image 112
Andrew Tomazos Avatar answered Sep 27 '22 20:09

Andrew Tomazos