Do rvalue references to const have any use?

C++C++11ConstantsRvalue Reference

C++ Problem Overview


I guess not, but I would like to confirm. Is there any use for const Foo&&, where Foo is a class type?

C++ Solutions


Solution 1 - C++

They are occasionally useful. The draft C++0x itself uses them in a few places, for example:

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

The above two overloads ensure that the other ref(T&) and cref(const T&) functions do not bind to rvalues (which would otherwise be possible).

Update

I've just checked the official standard N3290, which unfortunately isn't publicly available, and it has in 20.8 Function objects [function.objects]/p2:

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

Then I checked the most recent post-C++11 draft, which is publicly available, N3485, and in 20.8 Function objects [function.objects]/p2 it still says:

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

Solution 2 - C++

The semantics of getting a const rvalue reference (and not for =delete) is for saying:

  • we do not support the operation for lvalues!
  • even though, we still copy, because we can't move the passed resource, or because there is no actual meaning for "moving" it.

The following use case could have been IMHO a good use case for rvalue reference to const, though the language decided not to take this approach (see original SO post).


The case: smart pointers constructor from raw pointer

It would usually be advisable to use make_unique and make_shared, but both unique_ptr and shared_ptr can be constructed from a raw pointer. Both constructors get the pointer by value and copy it. Both allow (i.e. in the sense of: do not prevent) a continuance usage of the original pointer passed to them in the constructor.

The following code compiles and results with double free:

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

Both unique_ptr and shared_ptr could prevent the above if their relevant constructors would expect to get the raw pointer as a const rvalue, e.g. for unique_ptr:

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

In which case the double free code above would not compile, but the following would:

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

Note that ptr could still be used after it was moved, so the potential bug is not totally gone. But if user is required to call std::move such a bug would fall into the common rule of: do not use a resource that was moved.


One can ask: OK, but why T* const&& p?

The reason is simple, to allow creation of unique_ptr from const pointer. Remember that const rvalue reference is more generic than just rvalue reference as it accepts both const and non-const. So we can allow the following:

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

this wouldn't go if we would expect just rvalue reference (compilation error: cannot bind const rvalue to rvalue).


Anyhow, this is too late to propose such a thing. But this idea does present a reasonable usage of an rvalue reference to const.

Solution 3 - C++

They are allowed and even functions ranked based on const, but since you can't move from const object referred by const Foo&&, they aren't useful.

Solution 4 - C++

Besides std::ref, the standard library also uses const rvalue reference in std::as_const for the same purpose.

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

It is also used as return value in std::optional when getting the wrapped value:

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

As well as in 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;

This is presumably in order to maintain the value category as well as constness of the wrapper when accessing the wrapped value.

This makes a difference whether const rvalue ref-qualified functions can be called on the wrapped object. That said, I don't know any uses for const rvalue ref qualified functions.

Solution 5 - C++

I can't think of a situation where this would be useful directly, but it might be used indirectly:

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);
}

The T in g is T const, so f's x is an T const&&.

It is likely this results in a comile error in f (when it tries to move or use the object), but f could take an rvalue-ref so that it cannot be called on lvalues, without modifying the rvalue (as in the too simple example above).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionfredoverflowView Question on Stackoverflow
Solution 1 - C++Howard HinnantView Answer on Stackoverflow
Solution 2 - C++Amir KirshView Answer on Stackoverflow
Solution 3 - C++Gene BushuyevView Answer on Stackoverflow
Solution 4 - C++eerorikaView Answer on Stackoverflow
Solution 5 - C++Fred NurkView Answer on Stackoverflow