#опытным
В прошлый раз мы поговорили о том, что можно использовать константную правую ссылку для того, чтобы запретить принимать любые rvalue reference в функцию.
Для чего это может быть нужно?
Допустим, мы храним в поле класса в каком-то виде ссылку на объект. И нам бы очень не хотелось принимать в конструкторе rvalue reference, потому что возможно сразу же после выхода из конструктора для объектов вызовется деструктор и хана этим объектам. И встречаем UB из-за хранения битой ссылки.
Есть такой стандартный класс std::reference_wrapper и его функции помощники std::ref() и std::cref(). Поскольку std::reference_wrapper предполагает хранение ссылки только для lvalue, то стандарт удалил перегрузки std::ref() и std::cref(), которые принимают const T&&.
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
По той же самой причине такая перегрузка удалена у функции std::as_const, которая формирует левую ссылку на константный тип из аргумента.
template< class T >
constexpr std::add_const_t<T>& as_const( T& t ) noexcept;
Также константные правые ссылки используются в более сложных штуках, типа std::optional, когда нужно вернуть из него значение.
constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;
С этой же целью оно используется, например, и в std::get.
template< std::size_t I, class... Types >
constexpr const std::variant_alternative_t<I, std::variant<Types...>>&&
get( const std::variant<Types...>&& v );
template< class T, class... Types >
constexpr const T&& get( const std::variant<Types...>&& v );
В таких случаях использование const T&& оправдано передачей информации и о ссылочности типа, и о его константности. Это важно в обобщенном программировании, потому что никто не знает с каким типом будет работать шаблонная сущность. Вы вполне можете получить константный временный объект std::optional(да и любого другого объекта), это синтаксически корректно. И чтобы геттер его внутреннего значение отражал свойства обертки, приходится перегружать эти геттеры для любых возможных параметров. Так вот например методы std::optional упомянутые выше вызовутся только для временных константных объектов. И эти свойства отображаются в возвращаемом значении.
Также не стоит забывать, что константность объекта не накладывает ультимативных ограничений на использование объекта. Есть мутабельные и статические поля, которые можно изменять, и плевать они хотели на вашу константность. А также указатели. Мы не можем менять сам указатель, но можем изменить объект, на который он указывает. Это немного расширяет спектр возможностей использования константных правых ссылок, но не прям существенно. В голову пришел очевидный пример - pimpl idiom. Согласно этой идиоме класс хранит указатель на реализацию, в которой может лежать все, что угодно. Все операции, которые как-то изменяют состояние объекта, влияют на данные внутри указателя. Поэтому снаружи кажется, что объект и не изменился. Да и старый объект можно будет использовать. Непонятно только, зачем менять привычные традиции использования правых ссылок, но все же.
// MyClass.hpp
class MyClass {
public:
MyClass();
MyClass(int g_meat);
MyClass(const MyClass &&other); // const rvalue reference!
~MyClass();
int GetMeat() const;
private:
class Pimpl;
Pimpl *impl {};
};
// MyClass.cpp
class MyClass::Pimpl {
public:
int meat {42};
};
MyClass::MyClass() : impl {new Pimpl} { }
MyClass::MyClass(int g_meat) : MyClass() {
impl->meat = g_meat;
}
MyClass::MyClass(const MyClass &&other) : MyClass()
{
impl->meat = other.impl->meat;
other.impl->meat = 0;
}
MyClass::~MyClass() { delete impl; }
int MyClass::GetMeat() const {
return impl->meat;
}
// main.cpp
int main() {
const MyClass a {100500};
MyClass b (std::move(a)); // moving from const!
std::cout << a.GetMeat() << "\n"; // returns 0, b/c a is moved-from
std::cout << b.GetMeat() << "\n"; // returns 100500
}
Stay useful even if nobody understands you. Stay cool.
#template #cpp11 #STL