Есть ли смысл в ссылках rvalue на const?

Ответы:

78

Иногда они полезны. Сам черновик C ++ 0x использует их в нескольких местах, например:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Выше двух перегрузок гарантировать , что другие ref(T&)и cref(const T&)функцию не связываются с rvalues (что было бы возможно).

Обновить

Я только что проверил официальный стандарт N3290 , который, к сожалению, не является общедоступным, и он имеет в 20.8 объектах функций [function.objects] / p2:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Затем я проверил самый последний проект, опубликованный после C ++ 11, который является общедоступным, N3485 , и в 20.8 Function objects [function.objects] / p2 он все еще говорит:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Говард Хиннант
источник
Глядя на cppreference, кажется, что это уже не так. Есть идеи, почему? Какие-то другие места const T&&используются?
Pubby
58
Почему вы трижды включили один и тот же код в свой ответ? Я слишком долго пытался найти разницу.
typ1232
9
@ typ1232: Похоже, что я обновил ответ почти через 2 года после того, как ответил на него, из-за опасений в комментариях, что упомянутые функции больше не появляются. Я сделал копирование / вставку из N3290 и из последнего черновика N3485, чтобы показать, что функции все еще присутствуют. В то время, на мой взгляд, использование копирования / вставки было лучшим способом убедиться, что больше глаз, чем мой, могли подтвердить, что я не упускал из виду некоторые незначительные изменения в этих подписях.
Говард Хиннант
1
@kevinarpe: «другие перегрузки» (не показанные здесь, но в стандарте) принимают ссылки lvalue и не удаляются. Показанные здесь перегрузки лучше подходят для rvalue, чем перегрузки, которые принимают ссылки lvalue. Таким образом, аргументы rvalue привязываются к показанным здесь перегрузкам, а затем вызывают ошибку времени компиляции, поскольку эти перегрузки удаляются.
Ховард Хиннант,
1
Использование const T&&предотвращает глупое использование явных аргументов шаблона формы ref<const A&>(...). Это не очень веский аргумент, но цена const T&&за T&&него минимальна.
Говард Хиннант
6

Семантика получения ссылки на const rvalue (а не for =delete) заключается в том, чтобы сказать:

  • мы не поддерживаем операцию для lvalues!
  • даже несмотря на то, что мы все еще копируем , потому что мы не можем переместить переданный ресурс или потому что нет никакого реального смысла для его «перемещения».

Следующий вариант использования мог бы быть IMHO хорошим вариантом использования для ссылки rvalue на const , хотя язык решил не использовать этот подход (см. Исходное сообщение SO ).


Случай: конструктор интеллектуальных указателей из необработанного указателя

Обычно рекомендуется использовать make_uniqueи make_shared, но оба unique_ptrи shared_ptrмогут быть построены из необработанного указателя. Оба конструктора получают указатель по значению и копируют его. Оба позволяют (т.е. в смысле: не предотвращать ) непрерывное использование исходного указателя, переданного им в конструкторе.

Следующий код компилируется и дает двойное освобождение :

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

Оба unique_ptrи shared_ptrмогут предотвратить вышеуказанное, если их соответствующие конструкторы ожидают получить необработанный указатель как const rvalue , например, для unique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

В этом случае приведенный выше двойной бесплатный код не будет компилироваться, но будет следующее:

std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) };     // ok, rvalue

Обратите внимание, что его ptrможно было использовать после перемещения, поэтому потенциальная ошибка не исчезла полностью. Но если от пользователя требуется вызвать std::moveтакую ​​ошибку, это попадет под общее правило: не используйте перемещенный ресурс.


Можно спросить: хорошо, а почему именно T* const&& p ?

Причина проста - разрешить создание unique_ptr из константного указателя . Помните, что ссылка const rvalue является более общей, чем просто ссылка rvalue, поскольку она принимает как constи non-const. Таким образом, мы можем допустить следующее:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

это не пошло бы, если бы мы ожидали только ссылку rvalue (ошибка компиляции: невозможно привязать const rvalue к rvalue ).


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

Амир Кирш
источник
Я был среди людей с похожими идеями, но у меня не было поддержки, чтобы продвигаться вперед.
Red.Wave
"auto p = std :: unique_ptr {std :: move (ptr)};" не компилируется с ошибкой «ошибка вывода аргумента шаблона класса». Я думаю, это должно быть «unique_ptr <int>».
Zehui Lin
4

Они разрешены, и даже функции ранжируются на основе const, но поскольку вы не можете перейти от объекта const, на который ссылается const Foo&&, они бесполезны.

Гена Бушуев
источник
Что именно вы имеете в виду под «ранжированным» замечанием? Думаю, что-то связано с разрешением перегрузки?
fredoverflow
Почему вы не могли перейти от константы rvalue-ref, если у данного типа есть ctor перемещения, который принимает константу rvalue-ref?
Фред Нурк
6
@FredOverflow, рейтинг перегрузки следующий:const T&, T&, const T&&, T&&
Гена Бушуев
2
@Fred: Как вы двигаетесь, не изменяя источник?
fredoverflow
3
@Fred: изменяемые элементы данных или, возможно, перемещение для этого гипотетического типа не требует изменения элементов данных.
Фред Нурк
2

Помимо std :: ref , стандартная библиотека также использует ссылку на const rvalue в std :: as_const для той же цели.

template <class T>
void as_const(const T&&) = delete;

Он также используется как возвращаемое значение в std :: optional при получении обернутого значения:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

Как и в std :: get :

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

Предположительно это сделано для того, чтобы поддерживать категорию значения, а также постоянство оболочки при доступе к обернутому значению.

Это имеет значение, могут ли для обернутого объекта вызываться функции с определением const rvalue ref. Тем не менее, я не знаю, как использовать функции с квалификацией const rvalue ref.

eerorika
источник
1

Я не могу представить себе ситуацию, когда это было бы полезно напрямую, но это могло бы использоваться косвенно:

template<class T>
void f(T const &x) {
  cout << "lvalue";
}
template<class T>
void f(T &&x) {
  cout << "rvalue";
}

template<class T>
void g(T &x) {
  f(T());
}

template<class T>
void h(T const &x) {
  g(x);
}

T в g - это T const, поэтому f x - это T const &&.

Вероятно, это приводит к ошибке компиляции в f (когда он пытается переместить или использовать объект), но f может принимать rvalue-ref, чтобы его нельзя было вызвать для lvalues ​​без изменения rvalue (как в слишком простом пример выше).

Фред Нурк
источник