Функция && классификатор

Меня смущает следующий код:

struct test {
  void f() & {
    std::cout << "&" << std::endl;
  }
  void f() const& {
    std::cout << "const&" << std::endl;
  }
  void f() && {
    std::cout << "&&" << std::endl;
  }
  void f() const&& {
    std::cout << "const&&" << std::endl;
  }

  void g() & {
    std::cout << "& -> ";
    f();
  }
  void g() const& {
    std::cout << "const& -> " ;
    f();
  }
  void g() && {
    std::cout << "&& -> ";
    f();
  }
  void g() const&& {
    std::cout << "const&& -> ";
    f();
  }

  test() {} //allow default const construction
};

int main(int, char**) {
    test value;
    const test constant;

    value.g();
    constant.g();

    std::move(value).g();
    std::move(constant).g();
}

Когда я компилирую с clang 3.5, я получаю этот вывод:

& -> &
const& -> const&
&& -> &
const&& -> const&

Почему здесь отбрасывается квалификатор r-значения? И есть ли способ вызвать f из g с помощью правого классификатора?

Ответ 1

Вызов f() интерпретируется как (*this).f(). Результатом разыменования указателя всегда является lvalue, поэтому *this является lvalue и вызывается функция, соответствующая lvalue.

Такое поведение даже имеет смысл, по крайней мере для меня. В большинстве случаев объект, на который ссылается выражение rvalue, будет либо уничтожен в конце полного выражения (временного), либо в конце текущей области (автоматическая локальная переменная, которую мы выбираем std::move). Но когда вы находитесь внутри функции-члена, ни одно из них не является истинным, поэтому объект не должен рассматривать себя как rvalue, так сказать. Это также объясняет, почему имя параметра ссылочной функции rvalue является значением lvalue внутри функции.

Если вы хотите, чтобы вызываемый rvalue f вызывался, вы можете сделать это:

std::move(*this).f();

Ответ 2

Это происходит потому, что разыменование 1this всегда создает lvalue, независимо от того, указывает ли он на временный объект или нет.

Итак, когда вы пишете это:

f();

это на самом деле означает следующее:

this->f(); //or (*this).f()
           //either way 'this' is getting dereferenced here

Таким образом, перегрузка f(), которая написана для lvalue, вызывается из g() — и соответственно выполняется соответствующая корреляция.

Надеюсь, что это поможет.


1. Обратите внимание, что this является значением prvalue; только после разыменования он производит lvalue.

Ответ 3

ref-qualifiers влияют на параметр неявного объекта, §13.3.1/4:

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

  • "lvalue reference to cv X" для объявленных функций без ref-qualifier или с & ref-qualifier
  • "Rvalue ссылка на cv X" для функций, объявленных с помощью &&ref-qualifier

где X - класс, членом которого является член, а cv - cv-qualification в объявлении функции-члена.

Разрешение перегрузки, грубо говоря, выполняется на аргументе объекта для преобразования параметров объекта так же, как и любое другое преобразование параметра → . Однако f() преобразуется в (*this).f() (§9.3.1/3), а *this - значение l. §5.3.1/1:

Унарный оператор * выполняет косвенное направление: [...] , а результат - значение l, относящееся к объекту или функции, для которых выражение указывает.

Следовательно, предпочтительны перегрузки f с квалификатором & - на самом деле, перегрузки rvalue полностью игнорируются, поскольку инициализаторы ссылок на объекты rvalue должны быть rvalues ​​(последняя точка маркера в §8.5.3/5). Кроме того, неконстантные ссылки предпочтительнее, чем константы в разрешении перегрузки (§13.3.3.2/3, последняя маркерная точка относительно стандартных преобразований).

Ответ 4

Ну, для f() правильный классификатор всегда lvalue reference, так как вы используете его на this. Все ваши вызовы f - это не что иное, как this->f() и this всегда lvalue. Не имеет значения, что для внешнего мира объект rvalue this является lvalue для объекта внутри.