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
#include
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.
Answer
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.
No comments:
Post a Comment