Do rvalue references to const have any use?
I guess not, but I would like to confirm. Is there any use for const Foo&&
, where Foo
is a class type?
Solution 1:
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:
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:
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:
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:
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).