122

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

David G
  • 94,763
  • 41
  • 167
  • 253
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 2
    In this video, STL says `const&&` are very important, although he doesn't say why: https://www.youtube.com/watch?v=JhgWFYfdIho#t=54m20s – Aaron McDaid Oct 05 '16 at 20:28

8 Answers8

97

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;
gx_
  • 4,690
  • 24
  • 31
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Looking at [cppreference](http://en.cppreference.com/w/cpp/utility/functional/ref) it appears this isn't the case anymore. Any ideas why? Any other places `const T&&` is used? – Pubby Dec 13 '12 at 00:17
  • Actually, cppreference might just not display deleted functions. Haven't checked the standard. – Pubby Dec 13 '12 at 00:25
  • @Pubby cppreference certainly displays deleted functions, those were omitted by mistake. – Cubbi Feb 17 '13 at 17:12
  • 96
    Why did you include the same code in your answer three times? I tried to find a difference for too long. – typ1232 Jul 15 '13 at 00:16
  • 12
    @typ1232: It appears that I updated the answer nearly 2 years after I answered it, due to concerns in the comments that the referenced functions no longer appeared. I did a copy/paste from N3290, and from the then-lastest draft N3485 to show that the functions still appeared. Using copy/paste, in my mind at the time, was the best way to ensure that more eyes than mine could confirm that I wasn't overlooking some minor change in these signatures. – Howard Hinnant Jul 15 '13 at 03:31
  • 3
    Procedure for verifying equivalence of two snippets: copy the first snippet into a text file; copy an unrelated snippet into a second text file; copy the second snippet into a third text file. Use `diff` to verify the first and last files are equal, and that the second is different. – filipos May 05 '16 at 14:29
  • Can you explain your second sentence a little bit more? `The above two overloads...` I don't quite follow, but it seems like an important point. – kevinarpe Oct 03 '16 at 09:09
  • 1
    @kevinarpe: The "other overloads" (not shown here, but in the standard) take lvalue references, and are not deleted. The overloads shown here are a better match for rvalues than are the overloads that take lvalue references. So rvalue arguments bind to the overloads shown here, and then cause a compile time error because these overloads are deleted. – Howard Hinnant Oct 03 '16 at 19:34
  • @HowardHinnant What if the `T` is NOT a template parameter, but an explicitly given type such as `class Bar; void Foo(Bar &&)` and `class Bar; void Foo(Bar const &&)`? Does those two `Foo` make any difference? – Dean Seo Oct 05 '16 at 06:25
  • @DeanSeo: No difference in this context. – Howard Hinnant Oct 05 '16 at 15:25
  • 2
    Why isn't `T&&` sufficient? – xskxzr Jan 31 '18 at 05:44
  • 3
    The use of `const T&&` prevents somebody foolishly using explicit template args of the form `ref(...)`. That's not a terribly strong argument, but the cost of `const T&&` over `T&&` is pretty minimal. – Howard Hinnant Jan 31 '18 at 14:32
  • 1
    Minor comment here about this example, since I've come across this in our codebase where it is definitely wrong (because it silently turns a move into a copy). These templated functions aren't rvalue references; they are what Scott Meyers called universal references (not going to explain it here, google it). AFAICT the const in that context makes no functional difference.. But there is a difference between "void foo(Foo&& f)" and "void(const Foo&& f)" where in the second f is effectively const (this *doesn't* seem to happen with the universal references).. – Oliver Seiler Jan 06 '21 at 16:24
  • 3
    @OliverSeiler no, you're wrong. A forwarding reference is an rvalue reference to a cv-unqualified template parameter. That constraint is not the case here due to the const qualifier, i.e. they do not behave like forwarding references but are always just const rvalue references to T within the example. – Secundi Mar 29 '21 at 10:48
26

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.

Amir Kirsh
  • 12,564
  • 41
  • 74
  • 1
    I was amongst people with similar ideas, but I did not have the support to push forward. – Red.Wave Mar 08 '20 at 13:12
  • "auto p = std::unique_ptr { std::move(ptr) };" does not compile with error "class template argument deduction failed". I think it should be "unique_ptr". – Zehui Lin Aug 11 '20 at 08:10
  • @ZehuiLin the author specifically mentions that this is *not* how STL is implemented. The language decided to not take this approach. – Semin Park Jan 12 '23 at 04:56
  • @SeminPark construction from rvalue pointer does still *work* (as long as the template params are correct as they are after Zehui's comment), it just isn't required or useful in the actual STL. – Ryan McCampbell May 19 '23 at 21:25
6

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.

Gene Bushuyev
  • 5,512
  • 20
  • 19
  • What exactly do you mean by the "ranked" remark? Something to do with overload resolution, I guess? – fredoverflow Feb 08 '11 at 22:05
  • Why couldn't you move from a const rvalue-ref, if the given type has a move ctor that takes a const rvalue-ref? – Fred Nurk Feb 08 '11 at 22:09
  • 6
    @FredOverflow, the ranking of overload is this: `const T&, T&, const T&&, T&&` – Gene Bushuyev Feb 08 '11 at 22:10
  • @Fred Nurk, yes you can, but what sensible actions could that move constructor perform? – Gene Bushuyev Feb 08 '11 at 22:12
  • @GeneBushuyev: Moving? It is a move ctor. Of course *all* types won't have this move ctor, just like types don't all have a copy ctor. But the question is *any* use, not *what can you use always.* – Fred Nurk Feb 08 '11 at 22:12
  • 2
    @Fred: How do you move without modifying the source? – fredoverflow Feb 08 '11 at 22:49
  • 3
    @Fred: Mutable data members, or perhaps moving for this hypothetical type doesn't require modifying data members. – Fred Nurk Feb 08 '11 at 22:52
  • @Fred you could be very evil and copy ¬.¬ – KitsuneYMG Feb 09 '11 at 04:02
  • @KitsuneYMG (bit late to the game, but still), I'd actually say it's even evil to mention that method! A const&& can bind to a const&, so if a const&& is immovable, it should resolve to the const& copying overload. Providing (rather than omitting or deleting) a const&& overload implies that moving it is still possible. Even if well documented this implied promise about the interface is, as you say, "very evil". ;) – monkey0506 Jul 06 '13 at 22:57
  • Of course you can move from it. `const` means that the object itself is not changed, apart from mutable, static members (e.g. an `std::map` that indicates current owner for a type that's aware of its instances) might change. In fact it's useful if you're const-correct since then you might still use the old object. – lorro Aug 18 '16 at 17:35
  • You're allowed to assume that an rvalue reference parameter is the unique reference to the argument object (for the duration of the call) and does not alias other objects. This exclusive access provides the justification for "move" in the non-const case, but even when const the non-aliasing assumption can yield useful optimizations. In particular, `operator = (const Foo &&x)` does not need to worry about the possibility that `&x == this`. – Matthijs Nov 02 '18 at 05:16
  • @FredNurk const values are immovable. If you did a move it would result in a copy. – KeyC0de Sep 27 '19 at 08:14
6

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
2

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).

Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
1

Rvalue references are meant to allow moving data. So in the vast majority of case its use is pointless.

The main edge case you will find it is to prevent people to call a function with an rvalue:

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

The const version will cover all the edge cases, contrary to the non const version.


Here is why, consider this example:

struct My_object {
    int a;
};

template<class T>
void fun(const T& param) {
    std::cout << "const My_object& param == " << param.a << std::endl;
}

template<class T>
void fun( T& param) {
    std::cout << "My_object& param == " << param.a << std::endl;
}

int main() {

    My_object obj = {42};
    fun( obj );
    // output: My_object& param == 42

    const My_object const_obj = {64};
    fun( const_obj );
    // output: const My_object& param == 64

    fun( My_object{66} );
    // const My_object& param == 66
   
    return 0;
}

Now if you'd like to prevent someone using fun( My_object{66} ); since in the present case, it will be converted to const My_object&, you need to define:

template<class T>
void fun(T&& a) = delete;

And now fun( My_object{66} ); will throw an error, however, if some smarty pants programmer decides to write:

fun<const My_object&>( My_object{1024} );
// const My_object& param == 1024

This will work again and call the const lvalue overload version of that function... Fortunately we can put an end to such profanity adding const to our deleted overload:

template<class T>
void fun(const T&& a) = delete;
arkan
  • 610
  • 6
  • 13
0

Perhaps it could be considered useful in this context (coliru link):

#include <iostream>

// Just a simple class
class A {
public:
  explicit A(const int a) : a_(a) {}
  
  int a() const { return a_; }

private:
  int a_;
};

// Returning a const value - shouldn't really do this
const A makeA(const int a) {
    return A{a};
}

// A wrapper class referencing A
class B {
public:
  explicit B(const A& a) : a_(a) {}
  explicit B(A&& a) = delete;
  // Deleting the const&& prevents this mistake from compiling
  //explicit B(const A&& a) = delete;
  
  int a() const { return a_.a(); }
  
private:
  const A& a_;
};

int main()
{
    // This is a mistake since makeA returns a temporary that B
    // attempts to reference.
    auto b = B{makeA(3)};
    std::cout << b.a();
}

It prevents the mistake being compiled. There are clearly a bunch of other problems with this code that compiler warnings do pick up, but perhaps the const&& helps?

Rai
  • 1,328
  • 11
  • 20
0

It's somewhat disturbing how pretty much everyone in this thread (with the exception of @FredNurk and @lorro) misunderstand how const works, so allow me to chime in.

Const reference only forbids modifying the immediate contents of the class. Not only do we have static and mutable members which we very well can modify through a const reference; but we also can modify the contents of the class stored in a memory location referenced by a non-static, non-mutable pointer - as long as we don't modify the pointer itself.

Which is exactly the case of an extremely common Pimpl idiom. Consider:

// MyClass.h

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

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

Behold - a fully functional, const-correct move constructor which accepts const rvalue references.

ScumCoder
  • 690
  • 1
  • 8
  • 20