Почему нельзя перегружать только один ref-qualifier?

Судя по всему, перегрузка ref-квалификаторов не допускается — этот код не скомпилируется, если вы удалите & или && (только токены, а не их функции):

#include <iostream>

struct S {
    void f() &  { std::cout << "Lvalue" << std::endl; }
    void f() && { std::cout << "Rvalue" << std::endl; }
};

int main()
{
    S s;
    s.f();   // prints "Lvalue"
    S().f(); // prints "Rvalue"
}

Другими словами, если у вас есть две функции с одинаковым именем и типом, вы должны определить обе, если вы определяете любую из них. Я предполагаю, что это сделано намеренно, но в чем причина? Почему бы не разрешить, скажем, вызывать версию && для rvalue, если она определена, и «основную» версию f() для всего остального в следующем варианте (и наоборот — хотя это может сбивать с толку):

struct S {
    void f()    { std::cout << "Lvalue" << std::endl; }
    void f() && { std::cout << "Rvalue" << std::endl; }
};

Другими словами, пусть они действуют аналогично шаблонным специализациям по отношению к первичному шаблону.


person Lmn    schedule 14.02.2016    source источник
comment
Изменение значения void f() {...} в зависимости от других перегрузок звучит запутанно.   -  person chris    schedule 14.02.2016
comment
Никогда не видел, чтобы квалификатор &/&& использовался на практике.   -  person Lingxi    schedule 14.02.2016
comment
@Lingxi, одно из применений - превратить ловушки в ошибки компилятора. Например, люди могут по ошибке сделать const char* str = getStr().c_str();. Вы можете использовать это, чтобы превратить это в ошибку компилятора. К сожалению, в настоящее время нет способа сделать его надежным (т. е. быть ошибкой, но все еще работать в foo(getStr().c_str());). Я знаю, что это изучается в стандартных предложениях. Также см. этот вопрос для использования .   -  person chris    schedule 14.02.2016
comment
Кажется, в StackOverflow нет сомнений в правильном использовании квалификаторов ссылок.   -  person Lmn    schedule 14.02.2016
comment
Вам не нужно определять оба. Хорошо иметь только один. Например. void f() && без каких-либо других определений f   -  person M.M    schedule 14.02.2016
comment
Из-за [over.match.funcs] 13.3.1\5.3 функция-член без ref-qualifier действует как двуствольное ружье на lvalue s и rvalue. Таким образом, когда вы объявляете функцию-член с любым из сестер-близнецов ref-qulifier, этот дробовик должен быть удален из-за вносимой двусмысленности.   -  person Eugene Zavidovsky    schedule 14.02.2016


Ответы (3)


Это ничем не отличается от следующей ситуации:

struct S {};

void g(S s);
void g(S& s);

int main()
{
    S s;
    g(s);     // ambiguous
}

Разрешение перегрузки всегда работало таким образом; передача по ссылке не предпочтительнее передачи по значению (или наоборот).

(Разрешение перегрузки для функций с указанием ссылки работает так, как если бы это была обычная функция с неявным первым параметром, аргумент которого равен *this; квалифицированное значение lvalue-ref похоже на первый параметр S &, const & похоже на S const & и т. д.)

Я думаю, вы говорите, что g(s) должен вызывать g(S&), а не двусмысленно.

Я не знаю точного обоснования, но разрешение перегрузки достаточно сложно, поскольку оно не добавляет дополнительных особых случаев (особенно тех, которые могут молча компилироваться не так, как задумал кодер).

Как вы заметили в своем вопросе, проблемы можно легко избежать, используя две версии S & и S &&.

person M.M    schedule 14.02.2016
comment
Спасибо предложенному редактору .. принял бы, если бы увидел его до того, как кто-то отклонил :) - person M.M; 15.02.2016
comment
Сами можете добавить? Я думаю, что это ценное дополнение к вашему хорошему ответу. - person Lmn; 15.02.2016

Вы можете иметь и то, и другое. Нет особых требований по включению обоих, если вы реализуете один.

Загвоздка в том, что метод-член (нестатический член), не отмеченный квалификатором, подходит для использования как с lvalue, так и с rvalue. После того как вы перегружаете метод квалификатором ref, если вы не отметите и другие, вы столкнетесь с проблемами неоднозначности.

Во время разрешения перегрузки нестатическая cv-квалифицированная функция-член класса X обрабатывается как функция, которая принимает неявный параметр типа lvalue, ссылка на cv-qualified X, если она не имеет квалификаторов ref или если она имеет квалификатор ref lvalue. . В противном случае (если у него есть ref-qualifier rvalue), он обрабатывается как функция, принимающая неявный параметр типа rvalue, ссылающийся на cv-квалифицированный X.

Таким образом, в основном, если у вас есть один квалифицированный метод (например, для lvalue &) и один неквалифицированный, правила таковы, что они эффективно оба квалифицированы и, следовательно, неоднозначны.

Аналогичное обоснование применяется к квалификатору const. Вы можете реализовать метод и иметь одну «версию» для объекта const и одну для объекта, отличного от const. Контейнеры стандартной библиотеки являются хорошими примерами этого, в частности begin(), end() и другие методы, связанные с итераторами.

Один конкретный вариант использования — это когда логика, применяемая к методу, отличается между тем, когда объект является временным (или истекающим) объектом, и когда это не так. Вы можете оптимизировать внутреннюю обработку определенных вызовов и данных, если знаете, что время жизни подходит к концу.

Другой способ — ограничить использование метода значениями lvalue. Определенная часть логики приложения или объекта может не иметь смысла или быть бесполезной, если срок действия сущности скоро истечет или она является временной.


Формулировка в стандарте (взято из проекта N4567) из §13.4.1/4:

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

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

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

person Niall    schedule 15.02.2016
comment
Хорошее объяснение. Но почему именно в примере есть двусмысленность? - person Yam Marcovic; 15.02.2016
comment
@ЯмМаркович. Неквалифицированный член неявно квалифицирован для аргумента, следовательно, при наличии явного квалифицированного члена они эквивалентны... Я добавил цитату с сайта cppreference. - person Niall; 15.02.2016
comment
@YamMarcovic Для ответа на этот вопрос вас могут заинтересовать 13.3.3.2\3.2.3; 13.3.3\1. - person Eugene Zavidovsky; 15.02.2016
comment
@EugeneZavidovsky :) Сразу после комментария я открыл стандарт и изучил. Отсюда мой ответ. - person Yam Marcovic; 15.02.2016

Давайте начнем с того, что означает определение базовой нестатической функции-члена без каких-либо квалификаторов ref.

13.3.1 [4]

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

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

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

Но подождите, это еще не все.

[5] Для нестатических функций-членов, объявленных без квалификатора ref, применяется дополнительное правило:

даже если неявный объектный параметр не является константным, значение r может быть привязано к параметру, если во всех других отношениях аргумент может быть преобразован в тип неявного объектного параметра.

Поэтому

  1. Вы можете перегрузить только один тип ссылки: lvalue, rvalue
  2. Вы не можете перегрузить один или другой, а затем также добавить другой, который не квалифицирован по ссылке, потому что тот, который не квалифицирован по ссылке, определен для связывания обоих типов, и, следовательно, двусмысленность.
person Yam Marcovic    schedule 15.02.2016