Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 538
fredoverflow Avatar asked Feb 08 '11 21:02

fredoverflow


2 Answers

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; 
like image 61
Howard Hinnant Avatar answered Sep 23 '22 10:09

Howard Hinnant


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.

like image 23
Amir Kirsh Avatar answered Sep 25 '22 10:09

Amir Kirsh