Я смотрел видео Скотта Мейерса об универсальных ссылках, и он рассказал, что каждый раз, когда вы используете универсальную ссылку, вы должны пересылать ее так:
template<typename T>
auto func(T &&a) { return ++std::forward<T>(a); }
Тогда, выведя this
, мы имеем:
auto member_func(this auto &&self) { return self.member_variable; }
Где здесь std::forward<???>(self).member_variable
? Что я пропустил?
Я думаю, это объясняет это Явные функции-члены объекта «...Для шаблонов функций-членов явный параметр объекта позволяет выводить тип и категорию значения, эта языковая функция называется «выводом этого»:...»
Вопрос в том, почему вы не использовали std::forward
в этой функции? Я не знаю, почему ты этим не воспользовался.
Скотт Мейес ушел в отставку некоторое время назад. Это очень современный C++, о котором он не мог знать во время своего выступления (о котором вы говорите).
«должен» — это немного сильно. Вы можете пересылать явный параметр объекта так же, как и любой другой, по тем же причинам или нет.
@PepijnKramer Функция с явным параметром объекта, использующим auto
, является таким же шаблоном функции (члена), как и любой другой. this auto&&
— это ссылка на пересылку в сокращенном синтаксисе шаблона функции, как, например, void f(auto&& x) {}
.
Когда ссылка пересылки используется по имени, это lvalue. В любом другом коде, использующем параметр auto&& p
, ситуация ничем не отличается: вам нужно std::forward<decltype(p)>(p)
, чтобы передать ценность.
Я не думаю, что есть какая-либо разница в использовании его в этом примере кода с тривиальными типами. Категория значения возвращаемого значения может измениться, но тип возвращаемого значения — auto
, так что изменение в любом случае немедленно теряется, лишь способствуя формированию возвращаемого объекта. Вы можете проверить это, наблюдая за различиями: gcc.godbolt.org/z/e5Eoc9W1r
Обычно ссылку пересылки в явном параметре объекта следует пересылать так же, как и любую другую ссылку пересылки:
auto member_func(this auto &&self) {
return std::forward<decltype(self)>(self).member_variable;
}
В целом здесь нет ничего отличного от других переадресаций. Вы не указали, откуда взяли код, поэтому я не могу сказать, почему автор решил не пересылать код и является ли это проблемой в данном конкретном случае.
Однако, поскольку это явный объектный параметр функции-члена, вы обычно можете предположить, какой тип будет иметь self
, в отличие от большинства пересылающих ссылок в общих шаблонах функций. Это будет тип класса, в котором он написан, или тип производного класса.
В этом случае разумно предположить, что member_variable
будет относиться к члену member_variable
, объявленному в том же классе. И вы знаете, какой тип у этого участника.
Теперь предположим, что это выглядит так:
struct A {
int member_variable;
auto member_func(this auto &&self) { return self.member_variable; }
};
Тогда разумно предположить, что типом self.member_variable
является int
, и для int
совершенно не имеет значения, возвращается ли оно по значению (auto
) из выражения lvalue или rvalue. Известно, что вызов std::forward
не даст никакого эффекта.
Проблемы с этим:
Функция-член не ограничена типом класса, в котором она сама появляется. Если тип класса не отмечен final
или объявлена функция-член private
, то вполне возможно, что она будет вызвана для типа производного класса, который также определяет member_variable
другого типа. Это будет найдено для return self.member_variable;
и может иметь тип, при котором пересылка может иметь значение, например. std::string
.
Однако для меня не очевидно, рассматривал ли автор кода этот сценарий и действительно ли он хочет всегда возвращать member_variable
самого класса. В этом случае это должно быть self.A::member_variable
или что-то в этом роде. В этом случае все еще остаются проблемы с множественным и частным наследованием. (Однако существует решение для частного наследования.)
Если ссылка пересылки не пересылается, то в этом случае вообще нет смысла указывать явный параметр объекта this auto&&
. Его можно было бы просто объявить как this const auto&
или this const A&
, что также позволит избежать упомянутой выше проблемы. На самом деле тогда это может быть просто обычная нестатическая функция-член без явного параметра объекта.
Поскольку указатели функций на явные функции-члены могут быть сформированы, можно вызвать функцию-член косвенно через такой указатель с любым другим типом для self
. Аналогично, неявное преобразование к любому типу можно вызвать, явно указав аргумент шаблона для типа auto
. Однако это не обычные варианты использования, и функции, вероятно, не нужно их учитывать.
Кроме того, в целом важно помнить, что означает использование std::forward
. Наивное применение std::forward
к любому появлению переменной, которая является ссылкой на пересылку, неверно. std::forward
заставляет выражение, для которого он является операндом (потенциально) использовать состояние объекта. Обычно его можно применить только один раз в теле функции, после чего объект больше не следует использовать.
В случае выражения доступа к члену, например
return std::forward<decltype(self)>(self).member_variable;
это немного по-другому: здесь std::forward
вызывает потенциальное потребление только участника member_variable
. Остальные члены гарантированно останутся в своем предыдущем состоянии. Таким образом, можно подать заявку std::forward
один раз для каждого участника индивидуально.
Делаем вывод, что это не «классический» шаблон, он не обязательно должен быть обратно совместимым, чтобы понять, что ему нужно делать. По крайней мере, я так понимаю (буду рад, если меня поправят, если это тоже не так)