При работе с перегрузками функций с квалификацией ref я получаю разные результаты GCC (4.8.1) а также Лязг (2,9 и багажник). Рассмотрим следующий код:
#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();
}
лязг компилирует и выводит "const rvalue"
, в то время как НКУ считает, что это неоднозначный вызов с двумя константно квалифицированными функциями, обе из которых являются наиболее подходящими кандидатами. Если я предоставлю все 4 перегрузки, то оба компилятора выведут "non-const rvalue"
,
Я хотел бы знать, какой компилятор —если есть … делает правильные вещи, и каковы соответствующие стандартные пьесы в игре.
Замечания: Причина, по которой это на самом деле имеет значение, состоит в том, что реальный код объявляет обе функции с константой constexpr
, Конечно, нет выхода на std::cout
а также static_cast
используется вместо std::move
так, чтобы они действовали constexpr
определения. И так как в C ++ 11 constexpr
все еще подразумевает const
перегрузка, закомментированная в примере кода, не может быть предоставлена, так как она переопределит квалифицированную const перегрузку rvalue.
Во-первых, неявный параметр объекта обрабатывается как обычный параметр согласно 13.3.1.4:
Для не статических функций-членов тип неявного параметра объекта
— «lvalue ссылка на cv X» для функций, объявленных без квалификатора ref или с & реф-классификатор
— «rvalue ссылка на cv X» для функций, объявленных с && реф-классификатор
где X — класс, членом которого является функция, а cv — квалификация cv для этого члена.
объявление функции.
То, что вы спрашиваете, эквивалентно следующему:
void bar(foo&);
void bar(foo&&);
void bar(const foo&);
void bar(const foo&&);
int main()
{
bar(foo());
}
Выражение foo()
это класс prvalue.
Во-вторых, неконстантная эталонная версия lvalue нежизнеспособна, так как prvalue не может с ней связываться.
Это оставляет нам три жизнеспособные функции для разрешения перегрузки.
У каждого есть один неявный параметр объекта (const foo&
, foo&&
или же const foo&&
), поэтому мы должны оценить эти три, чтобы определить лучший матч.
Во всех трех случаях это напрямую связаны Ссылка обязательна. Это описано в описателях / инициализации (8.5.3).
Ранжирование трех возможных привязок (const foo&
, foo&&
а также const foo&&
) описывается в 13.3.3.2.3:
Стандартная последовательность преобразования S1 — лучшая последовательность преобразования, чем стандартная последовательность преобразования S2 если
- S1 и S2 являются ссылочными привязками, и ни одна из них не ссылается на неявный объектный параметр нестатической функции-члена, объявленной без квалификатора ref (это исключение здесь не применимо, все они имеют квалификаторы ref], и S1 связывает rvalue ссылку на rvalue [класс prvalue является rvalue] и S2 связывает ссылку lvalue.
Это означает, что оба foo&&
а также const foo&&
тогда лучше const foo&
,
- S1 и S2 являются привязками ссылок, и типы, на которые ссылаются ссылки, относятся к одному и тому же типу, за исключением cv-квалификаторов верхнего уровня, и типа, к которому ссылка инициализируется Ссылка S2 более cv-квалифицирована, чем тип, к которому относится ссылка, инициализированная S1.
Это означает, что foo&&
лучше, чем const foo&&
,
Так что Clang прав, и это ошибка в GCC. Рейтинг перегрузки для foo().bar()
как следует:
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;
};
Ошибка в GCC, кажется, применяется исключительно к неявным параметрам объекта (с ref-qualifiers
), для нормального параметра, похоже, правильное ранжирование, по крайней мере в 4.7.2.
Других решений пока нет …