Разрешение перегрузки с помощью квалификаторов ref

При работе с перегрузками функций с указанием ссылки я получаю разные результаты от GCC (4.8.1) и Clang (2.9 и trunk). Рассмотрим следующий код:

#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 компилирует его и выводит "const rvalue", в то время как GCC считает, что это неоднозначный вызов, поскольку обе функции с квалификацией const являются наиболее подходящими кандидатами. Если я предоставлю все 4 перегрузки, оба компилятора выведут "non-const rvalue".

Я хотел бы знать, какой компилятор - если есть - делает правильные вещи и какие стандартные элементы используются в игре.

Примечание. Причина, по которой это действительно имеет значение, заключается в том, что в реальном коде обе функции с указанием const объявляются как constexpr. Конечно, в std::cout нет вывода, а вместо std::move используется static_cast, так что они являются действительными constexpr определениями. И поскольку в C ++ 11 constexpr по-прежнему подразумевается const, перегрузка, закомментированная в примере кода, не может быть предоставлена, так как она переопределит перегрузку rvalue с константой.


person K-ballo    schedule 16.06.2013    source источник


Ответы (1)


Во-первых, неявный параметр объекта рассматривается как обычный параметр согласно 13.3.1.4:

Для нестатических функций-членов тип неявного параметра объекта -

- «ссылка lvalue на cv X» для функций, объявленных без квалификатора ref или с квалификатором & ref

- «ссылка rvalue на cv X» для функций, объявленных с квалификатором && ref

где 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&&.

Итак, Кланг прав, и это ошибка 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.

person Andrew Tomazos    schedule 16.06.2013
comment
Ссылка rvalue является лучшим выбором во всех случаях. Скорее всего, это ошибка с квалификаторами ссылок GGC. - person Xeo; 16.06.2013
comment
Кроме того, этот ответ может быть актуальным. ;) - person Xeo; 16.06.2013
comment
Спасибо за подробный ответ. Хотел бы я проголосовать за него больше одного раза! - person K-ballo; 16.06.2013