Like it!

Join us on Facebook!

Like it!

C++ rvalue references and move semantics for beginners

A collection of personal notes and thoughts on rvalue references, their role in move semantics and how they can significantly increase the performance of your applications.

In my previous article Understanding the meaning of lvalues and rvalues in C++ I had the chance to explain to myself the logic behind rvalues. The core idea is that in C++ you will find such temporary, short-lived values that you cannot alter in any way.

Surprisingly, modern C++ (C++0x and greater) has introduced rvalue references: a new type that can bind to temporary objects, giving you the ability to modify them. Why?

Let's begin this journey with a little brush up of temporary values:

int x = 666;                    // (1)
int y = x + 5;                  // (2)

std::string s1 = "hello ";
std::string s2 = "world";
std::string s3 = s1 + s2;       // (3)

std::string getString() {
  return "hello world";
}
std::string s4 = getString();   // (4)

On line (1) the literal constant 666 is an rvalue: it has no specific memory address, except for some temporary register while the program is running. It needs to be stored in a lvalue (x) to be useful. Line (4) is similar, but here the rvalue is not hard-coded, rather it is being returned by the function getString(). However, as in line (1), the temporary object must be stored in an lvalue (s4) to be meaningful.

Lines (2) and (3) seem more subtle: the compiler has to create a temporary object to hold the result of the + operator. Being a temporary one, the output is of course an rvalue that must be stored somewhere. And that's what I did by putting the results in y and s3 respectively.

Introducing the magic of rvalue references

The traditional C++ rules say that you are allowed to take the address of an rvalue only if you store it in a const (immutable) variable. More technically, you are allowed to bind a const lvalue to an rvalue. Consider the following example:

int& x = 666;       // Error
const int& x = 666; // OK

The first operation is wrong: it's an invalid initialization of non-const reference of type int& from an rvalue of type int. The second line is the way to go. Of course, being x a constant, you can't alter it.

C++0x has introduced a new type called rvalue reference, denoted by placing a double ampersand && after some type. Such rvalue reference lets you modify the value of a temporary object: it's like removing the const attribute in the second line above!

Let's play a bit with this new toy:

  std::string   s1     = "Hello ";
  std::string   s2     = "world";
  std::string&& s_rref = s1 + s2;    // the result of s1 + s2 is an rvalue
  s_rref += ", my friend";           // I can change the temporary string!
  std::cout << s_rref << '\n';       // prints "Hello world, my friend"

Here I create two simple strings s1 and s2. I join them and I put the result (a temporary string, i.e. an rvalue) into std::string&& s_rref. Now s_rref is a reference to a temporary object, or an rvalue reference. There are no const around it, so I'm free to modify the temporary string to my needs. This wouldn't be possible without rvalue references and its double ampersand notation. To better distinguish it, we refer to traditional C++ references (the single-ampersand one) as lvalue references.

This might seem useless at a first glance. However rvalue references pave the way for the implementation of move semantics, a technique which can significantly increase the performance of your applications.

Move semantics, the scenic route

Move semantics is a new way of moving resources around in an optimal way by avoiding unnecessary copies of temporary objects, based on rvalue references. In my opinion, the best way to understand what move semantics is about is to build a wrapper class around a dynamic resource (i.e. a dynamically allocated pointer) and keep track of it as it moves in and out functions. Keep in mind however that move semantics does not apply only to classes!

That said, let's take a look at the following example:

class Holder
{
  public:

    Holder(int size)         // Constructor
    {
      m_data = new int[size];
      m_size = size;
    }

    ~Holder()                // Destructor
    {
      delete[] m_data;
    }

  private:

    int*   m_data;
    size_t m_size;
};

It is a naive class that handles a dynamic chunk of memory: nothing fancy so far, except for the allocation part. When you choose to manage the memory yourself you should follow the so-called Rule of Three. This rule states that if your class defines one or more of the following methods it should probably explicitly define all three:

  • destructor;
  • copy constructor;
  • copy assignment operator.

A C++ compiler will generate them by default if needed, in addition to the constructor and other functions we don't care about right now. Unfortunately the default versions are just "not enough" when your class deals with dynamic resources. Indeed, the compiler couldn't generate a constructor like the one in the example above: it doesn't know anything about the logic of our class.

Implementing the copy constructor

Let's stick to the Rule of Three and implement the copy constructor first. As you may know, the copy constructor is used to create a new object from another existing object. For example:

Holder h1(10000); // regular constructor
Holder h2 = h1;   // copy constructor
Holder h3(h1);    // copy constructor (alternate syntax)

How a copy constructor would look like:

Holder(const Holder& other)
{
  m_data = new int[other.m_size];  // (1)
  std::copy(other.m_data, other.m_data + other.m_size, m_data);  // (2)
  m_size = other.m_size;
}

Here I'm initializing a new Holder object out of the existing one passed in as other: I create a new array of the same size (1) and then I copy the actual data from other.m_data to m_data (i.e. this.m_data) (2).

Implementing the assignment operator

It's now time for the assignment operator, used to replace an existing object with another existing object. For example:

Holder h1(10000);  // regular constructor
Holder h2(60000);  // regular constructor
h1 = h2;           // assignment operator

How an assigment operator would look like:

Holder& operator=(const Holder& other) 
{
  if(this == &other) return *this;  // (1)
  delete[] m_data;  // (2)
  m_data = new int[other.m_size];
  std::copy(other.m_data, other.m_data + other.m_size, m_data);
  m_size = other.m_size;
  return *this;  // (3)
}

First of all a little protection against self-assignment (1). Then, since we are replacing the content of this class with another one, let's wipe out the current data (2). What's left is just the same code we wrote in the copy constructor. By convention a reference to this class is returned (3).

The key point of the copy constructor and the assignment operator is that they both receive a const reference to an object in input and make a copy out of it for the class they belong to. The object in input, being a constant reference, is of course left untouched.

The limitations of our current class design

Our class is good to go, but it lacks of some serious optimization. Consider the following function:

Holder createHolder(int size)
{
  return Holder(size);
}

It returns a Holder object by value. We know that when a function returns an object by value, the compiler has to create a temporary — yet fully-fledged — object (rvalue). Now, our Holder is a heavy-weight object due to its internal memory allocation, which is a very expensive task: returning such things by value with our current class design would trigger multiple expensive memory allocations, which is rarely a great idea. How come? Consider this:

int main()
{
  Holder h = createHolder(1000);
}

A temporary object coming out from createHolder() is passed to the copy constructor. According to our current design, the copy constructor allocates its own m_data pointer by copying the data from the temporary object. Two expensive memory allocations: a) during the creation of the temporary, b) during the actual object copy-construct operation.

The same copy procedure occurs within the assignment operator:

int main()
{
  Holder h = createHolder(1000); // Copy constructor
  h = createHolder(500);         // Assignment operator
}

The code inside our assignment operator wipes the memory out and then reallocates it from scratch by copying the data from the temporary object. Yet another two expensive memory allocations: a) during the creation of the temporary, b) in the actual object assignment operator.

Too many expensive copies! We already have a fully-fledged object, the temporary and short-lived one returning from createHolder(), built for us by the compiler: it's an rvalue that will fade away with no use at the next instruction: why, during the construction/assignment stages, don't we steal — or move the allocated data inside the temporary object instead of making an expensive copy out of it?

In the old days of C++ there was no way to optimize this out: returning heavy-weight objects by value was simply a no-go. Fortunately in C++11 and greater we are allowed (and encouraged) to do this, by improving our current Holder class with move semantics. In a nutshell, we will steal existing data from temporary objects instead of making useless clones. Don't copy, just move, because moving is always cheaper.

Implementing move semantics with rvalue references

Let's spice up our class with move semantics: the idea is to add new versions of the copy constructor and assignment operator so that they can take a temporary object in input to steal data from. To steal data means to modify the object the data belongs to: how can we modify a temporary object? By using rvalue references!

At this point we naturally follow another C++ pattern called the Rule of Five. It's an extension to the Rule of Three seen before and it states that any class for which move semantics are desirable, has to declare two additional member functions:

  • the move constructor — to construct new objects by stealing data from temporaries;
  • the move assignment operator — to replace existing objects by stealing data from temporaries.

Implementing the move constructor

A typical move constructor:

Holder(Holder&& other)     // <-- rvalue reference in input
{
  m_data = other.m_data;   // (1)
  m_size = other.m_size;
  other.m_data = nullptr;  // (2)
  other.m_size = 0;
}

It takes in input an rvalue reference to another Holder object. This is the key part: being an rvalue reference, we can modify it. So let's steal its data first (1), then set it to null (2). No deep copies here, we have just moved resources around! It's important to set the rvalue reference data to some valid state (2) to prevent it from being accidentally deleted when the temporary object dies: our Holder destructor calls delete[] m_data, remember? In general, for reasons that will become more clear in a few paragraphs, it's a good idea to always leave the objects being stolen from in some well-defined state.

Implementing the move assignment operator

The move assignment operator follows the same logic:

Holder& operator=(Holder&& other)     // <-- rvalue reference in input  
{  
  if (this == &other) return *this;

  delete[] m_data;         // (1)

  m_data = other.m_data;   // (2)
  m_size = other.m_size;

  other.m_data = nullptr;  // (3)
  other.m_size = 0;

  return *this;
}

We steal data (2) from the other object coming in as an rvalue reference, after a cleanup of the existing resources (1). Let's not forget to put the temporary object to some valid state (3) as we did in the move constructor. Everything else is just regular assignment operator duty.

Now that we have our new methods in place, the compiler is smart enough to detect whether you are creating an object with a temporary value (rvalue) or a regular one (lvalue) and trigger the proper constructor/operator accordingly. For example:

int main()
{
  Holder h1(1000);                // regular constructor
  Holder h2(h1);                  // copy constructor (lvalue in input)
  Holder h3 = createHolder(2000); // move constructor (rvalue in input) (1) 

  h2 = h3;                        // assignment operator (lvalue in input)
  h2 = createHolder(500);         // move assignment operator (rvalue in input)
}

Where and when move semantics apply

Move semantics provide a smarter way of passing heavy-weight things around. You create your heavy-weight resource only once and then you move it where needed in a natural way. As I said before, move semantics is not only about classes. You can make use of it whenever you need to change the ownership of a resource across multiple areas of your application. However keep in mind that, unlike a pointer, you are not sharing anything: if object A steals data from object B, data in object B no longer exists, thus is no longer valid. As we know this is not a problem when dealing with temporary objects, but you can also steal from regular ones. We will see how shortly.

I tried your code: the move constructor never gets called!

That's right. If you run the last snippet above you will notice how the move constructor does not get called during (1). The regular constructor is called instead: this is due to a trick called Return Value Optimization (RVO). Modern compilers are able to detect that you are returning an object by value, and they apply a sort of return shortcut to avoid useless copies.

You can tell the compiler to bypass such optimization: for example, GCC supports the -fno-elide-constructors flag. Compile the program with such flag enabled and run it again: the amount of constructor/destructor calls will increase noticeably.

Why should I care implementing move semantics if the RVO does its optimization job by default?

RVO is only about return values (output), not function parameters (input). There are many places where you may pass movable objects as input parameters, which would make the move constructor and the move assignment operator come into play, if implemented. The most important one: the Standard Library. During the upgrade to C++11 all the algorithms and containers in there were extended to support move semantics. So if you use the Standard Library with classes that follow the Rule of Five you will gain an important optimization boost.

Can I move lvalues?

Yes you can, with the utility function std::move from the Standard Library. It is used to convert an lvalue into an rvalue. Say we want to steal from an lvalue:

int main()
{
  Holder h1(1000);     // h1 is an lvalue
  Holder h2(h1);       // copy-constructor invoked (because of lvalue in input)
}

This will not work: since h2 receives an lvalue in input, the copy constructor is being triggered. We need to force the move constructor on h2 in order to make it steal from h1, so:

int main()
{
  Holder h1(1000);           // h1 is an lvalue
  Holder h2(std::move(h1));  // move-constructor invoked (because of rvalue in input)
}

Here std::move has converted the lvalue h1 into an rvalue: the compiler sees such rvalue in input and then triggers the move constructor on h2. The object h2 will steal data from h1 during its construction stage.

Mind that at this point h1 is a hollow object. However, we did a good thing when in our move constructor we set the stolen object's data to a valid state (other.m_data = nullptr, remember?). Now you may want to reuse h1, test it in some way or let it go out of scope without causing nasty crashes.

Final notes and possible improvements

This article is way too long and I've only scratched the surface of move semantics. What follows is a quick list of additional concepts I will further investigate in the future.

We did RAII in our basic Holder example

Resource Acquisition Is Initialization (RAII) is a C++ technique where you wrap a class around a resource (file, socket, database connection, allocated memory, ...). The resource is initialized in the class constructor and cleaned up in the class destructor. This way you are sure to avoid resource leaks. More information: here.

Mark you move constructors and move assignment operators with noexcept

The C++11 keyword noexcept means "this function will never throw exceptions". It is used to optimize things out. Some people say that move constructors and move assignment operators should never throw. Rationale: you should not allocate memory or call other code in there. You should only copy data and set the other object to null, i.e. non-throwing operations. More information: here, here.

Further optimizations and stronger exception safety with copy-and-swap idiom

All the constructors/assignment operators in the Holder class are full of duplicate code, which is not so great. Moreover, if the allocation throws an exception in the copy assignment operator the source object might be left in a bad state. The copy-and-swap idiom fixes both issues, at the cost of adding a new method to the class. More information: here, here.

Perfect forwarding

This technique allows you to move your data across multiple template and non-template functions without wrong type conversions (i.e. perfectly). More information: here, here.

Sources

Stack Overflow - When is an rvalue evaluated? (link)
Mikw's C++11 blog - Lesso #5: Move Semantics (link)
Artima - A Brief Introduction to Rvalue References (link)
Stack Overflow - C++11 rvalues and move semantics confusion (return statement) (link)
Cpp-patterns - The rule of five (link)
open-std.org - A Brief Introduction to Rvalue References (link)
Microsoft - Rvalue Reference Declarator: && (link)
Wikipedia - Rule of three (C++ programming) (link)
Stack Overflow - What are all the member-functions created by compiler for a class? Does that happen all the time? (link)
cplusplus.com - Copy constructors, assignment operators, and exception safe assignment (link)
Stack Overflow - What is the copy-and-swap idiom? (link)
Wikipedia - Assignment operator (C++) (link)
Stack Overflow - When the move constructor is actually called if we have (N)RVO? (link)
cppreference.com - The rule of three/five/zero (link)
cprogramming.com - Move semantics and rvalue references in C++11 (link)
Stack Overflow - What is std::move(), and when should it be used? (link)

comments
James on June 11, 2018 at 06:59
Nice article that helps convey the reasoning behind this new feature in an easy to understand way. Minor bug in the the move assignment operator's self-assignment check: Should be ==. I'd also suggest that zero'ing out m_size in addition to nulling out the m_data pointer would help reinforce the idea of keeping the object in a valid state post-move.
Jeremy on June 11, 2018 at 07:08
This was a really excellent write up. I look forward to reading your walk throughs of the other topics mentioned. Great work!
▲s on June 11, 2018 at 22:01
Thanks @James for your hints, article updated!
▲s on June 11, 2018 at 22:02
@Jeremy thank you for your kind words <3
Wassili on June 19, 2018 at 16:05
good article, but "Perfect forwarding" works only with templates
Johan on June 20, 2018 at 13:45
The text says "Now s_rref is a reference to a temporary object, or an rvalue reference."

I think it's important to understand that a named rvalue reference is actually a lvalue. This can be a bit confusing at first.
ubiratans on June 21, 2018 at 15:29
Very good and interesting article! Congrats!
aka on November 19, 2018 at 00:26
Very good article. thanks
mtanh on November 25, 2018 at 08:41
Great article. Thanks
Artyom on December 13, 2018 at 13:46
Thanks a lot for crisp explanations.
When I'm trying to explain or write similar examples, for preventing RVO, I often add a simple if-condition. Usually it breaks RVO and makes things clearer.
akis on February 14, 2019 at 09:59
Thank you very much for taking the effort to explain this difficult concept. I am looking forward for your next topics
Daniel on February 17, 2019 at 21:28
Thanks! The kind of explanation that makes me happy to read.
wj on February 17, 2019 at 22:50
You are a scholar, sir!
xyz on February 22, 2019 at 21:10
thanks
RP on March 14, 2019 at 08:38
Great article!
I am wondering about one thing: suppose having a class holding dynamic (via pointer) as well as ?static? (non ptr) resources. For dynamic resources I understand that copying the pointer is fine, but this only works for pointers or integral types, right? Suppose I have a bigger chunk of data stored in a data member DATA_, would I have to perform DATA_ = std::move(other.DATA_) instead of DATA_ = other.DATA_ ? Because, basically, even if we are dealing with an rvalue reference, the data members are still lvalues, right? This also got way to long. Anyway, thanks for this awesome blog. Keep up the great work!
vaibhav on March 24, 2019 at 14:04
Awsome , I was trying hard with such type of explanation on move , great work.....thanks
fhabermacher on March 25, 2019 at 12:44
Thank you, really useful description!
Maigo on April 05, 2019 at 07:31
This is the best article I've read on rvalue references and move semantics! It keeps the basic simple, and leaves more advanced topics (e.g. return value optimization, copy-and-swap idiom, perfect forwarding) to the end!
Garfield on April 24, 2019 at 18:50
Thanks! perfect explaination!
Sameh Hassanein on May 02, 2019 at 20:41
Excellent Work Thanks
malviyanshiv on May 07, 2019 at 15:03
Great article ...helped a lot
Kailasa Marathe on May 12, 2019 at 18:25
Very helpful. Hope to see more on other topics too.
Carlos Reyes on May 29, 2019 at 02:02
The C++ standard says that a compiler has to implement the return value optimization (RVO) when possible. At least one of your examples of bad code would actually be optimized into efficient code. In fact, move semantics are not as useful as appears at first glance because of compiler optimizations. Compilers were already pretty good at optimizing away redundant data copies in many cases.
Misha on June 15, 2019 at 13:11
In Holder createHolder(int size) copy constructor won't be called.

To have it called I had to allocate Holder* with new and return *h.
Enes Hecan on June 16, 2019 at 11:16
You are good teacher sir!
Wasin on July 08, 2019 at 04:50
Hey good article! I learned a ton with this.

To add a thing on top.
I use g++ (7.4.0) on Linux, and it optimizes away having to create a temporary variable thus avoid calling copy constructor completely. I have to add -fno-elide-constructors to force it to call it.
César on July 26, 2019 at 16:08
Great article! One comment though.

I've followed the coded to the dot and when

Holder h3 = createHolder(2000);

is executed, oddly enough only the regular constructor is invoked! I did then

Holder h3 = std::move(createHolder(2000));

and then the regular and move constructor were called, as expected. Do you now why?

Thanks for sharing! It is really appreciated :)
Triangles on August 03, 2019 at 10:48
@César, it is likely that your compiler is performing some clever optimizations. Take a look at the paragraph "I tried your code: the move constructor never gets called!", it's the RVO in action!
Sarah on October 24, 2019 at 09:43
Hi, the example to show the use of T&& with strings does not seem the best to me. Indeed when doing "string s = s1 + s2", you can also use "s+= "my friend". So there is no need to use string&& in that particular example. Am I wrong?
Triangles on October 27, 2019 at 10:31
@Sarah sure, you can also use "s += ..." when doing "string s = s1 + s2". However the string&& version shows you that you can alter the value of a temporary (where the temporary is the result of s1 + s2).
baixiangcpp on November 06, 2019 at 10:33
very good article.
Yue on November 27, 2019 at 07:32
Great writing. Thank you!
Dsonophorus on December 15, 2019 at 01:24
Thanks, very clean presentation of a detailed and important topic!
mindentropy on December 21, 2019 at 05:22
I am assuming you do not do null check in the destructor because delete is harmless on a nullptr. So when the temporary object goes out of scope the destructor gets called and there would be delete on the nullptr with no problems.
Rajesh Kumar on January 15, 2020 at 10:57
Excellent piece of article...Thank you...!
Siddhartha Agarwal on January 23, 2020 at 00:15
Very nicely explained. Enjoyed reading
pye on February 02, 2020 at 08:31
Great article!
Yujie on February 06, 2020 at 17:27
Great thanks
ZZ on March 15, 2020 at 06:12
The best teaching material ever!
RAII on April 04, 2020 at 12:29
Thanx for this awsome writeup! We appreciate your work !! :)
craseeedoood on April 17, 2020 at 09:10
This and the article on lvalues/rvalues were incredibly lucid and effective explanations, better than any resource I could find. Thank you so much man!!
samira on May 17, 2020 at 17:35
"More technically, you are allowed to bind a const lvalue to an rvalue. "
Shouldn't the sentence above is rewritten as following:
"More technically, you are allowed to bind a const rvalue to an lvalue. "
Mahmud on June 05, 2020 at 13:44
This is one of the best article to understand 'rvalue & std::move' currently out there. Very well written and easy to understand for beginners, I wish I could find this earlier.

Thank you so very much :)
MJS on June 20, 2020 at 04:26
Really informative article and makes what can be a hard concept to understand (especially if reading the spec) easy to grok.
URZq on June 30, 2020 at 14:52
After reading "effective modern c++" a few years ago, I needed a refresher for a job interview. This article was exactly what I needed, thank you.

The mention of perfect forwarding triggered some PTSD. Does anyone use that in the real world ? :D

Alexpanda on July 08, 2020 at 05:20
Great article, well-explained! Thank you!
Jaswant on July 08, 2020 at 10:43
Perfect explanation! Thank you very much! :)
Amarghosh on October 20, 2020 at 12:53
I googled for ELI5 std::move and it eventually took me here. Thanks for explaining it in an easy to understand way with examples. Appreciate the effort.
andy_li on January 02, 2021 at 17:42
This is a really really useful article, thank you.
Tarun_K on January 05, 2021 at 18:09
Thank you so much. Explained in very simple way and clearly
SuperKint on February 06, 2021 at 07:42
Wonderful c++Practice. Thanks for ariticle
HypertextAssassin0273 on February 25, 2021 at 07:28
Well Explained!!!
David on February 25, 2021 at 19:51
Wow, this is a BEAST. Very easy to understand, but also thorough enough that doesn't miss out any detail. I was pleasantly surprised you mentioned the GCC compiler thing :) This article should be the official introduction guide to Move Semantics for any beginner, easily outshines any other articles out there. I really enjoyed reading it and the "journey" :))
Heavynest on March 07, 2021 at 22:01
Nice post. Thanks
Koder on March 27, 2021 at 14:59
Great article, well explained!
idnsunset on May 08, 2021 at 10:05
Very clear step-by-step explanation for where and why that rvalue reference comes. It saved me from reading those tedious and obscure articles.
Tony on May 19, 2021 at 22:13
I learned so much, thank you!!!!! <3 <3 <3 >w<
Prantick on May 31, 2021 at 14:49
Genuinely wonderful article... clears so many rudimentary concepts. Keep up the good work!!
Mihai on September 14, 2021 at 19:42
DAMN this is very good, thank you so much!!!
cast on September 26, 2021 at 03:15
shouldn't it be?
Holder h3(std::move(createHolder(2000)));
to invoke a move construct from a rvalue:
HanChen on November 21, 2021 at 00:52
This is an excellent tutorial, thanks so much!
There is another great explanation:
https://stackoverflow.com/questions/37935393/pass-by-value-vs-pass-by-rvalue-reference/37956725
Ivan Bilicki on December 05, 2021 at 17:59
This is a great article, thank you! Just one comment.
"RVO is only about return values (output), not function parameters (input). There are many places where you may pass movable objects as input parameters, which would make the move constructor and the move assignment operator come into play, if implemented."
An example of this would be really helpful.
Amulya on March 09, 2022 at 11:41
Thank you for such an amazing article. You made the concepts so clear!
wawa on April 14, 2022 at 04:23
Thank you for your explanation!!! :)
Robert Pawlak on June 03, 2022 at 13:14
"The C++11 keyword noexcept means "this function will never throw exceptions" "

No, it means: if function will throw exception then behaviour is undefined.
Basically you can throw exception in noexcept function, but it has to be catched inside.
Robert Kent on September 01, 2022 at 03:48
Very helpful, thank you
Andy on September 26, 2022 at 05:38
Thanks for your detail explanation! It helps me much!
Chun Hu on September 27, 2022 at 20:50
Thank you for the clear explanation!
Iman on October 14, 2022 at 17:37
Great explanation with full details.
Kent Zhang on November 05, 2022 at 14:33
Thank you for sharing! This article helps me a lot!
Nevzat on November 27, 2022 at 04:10
Thanks for the effort!
oyiboebiye@gmail.com on January 10, 2023 at 13:43
Thanks alot for your Awesome explanation
---- on June 22, 2023 at 19:48
Thank you for the clear explanation!
Choi Myeongsu on July 08, 2023 at 14:32
<3
vtk on July 23, 2023 at 09:11
Awesome article. please continue your journey of writing articles in simple way. God bless you.
iisthphir on September 21, 2023 at 15:53
Seems rather arbitrary to make a const & bindable to an rvalue but not the same for a non const &.
If your going to implicitly create a memory location for an rvalue why should that have anything to do with whether what is going to live there is a constant or not?
I guess it was a choice to make the intent more visible or somesuch?
GLoad on September 21, 2023 at 17:00
Awesome