Sarien
Sarien

Reputation: 6992

When and why do I get a copy when initializing a reference?

There are cases when I want a reference to an object but instead I get a copy. Here is an example:

  std::pair<const std::string, int> foo("hello", 5);
  const std::pair<std::string, int> & bar = foo;

  std::cout << "foo: " << foo.first << " " << foo.second << std::endl;
  std::cout << "bar: " << bar.first << " " << bar.second << std::endl;
  foo.second = 7;
  std::cout << "foo: " << foo.first << " " << foo.second << std::endl;
  std::cout << "bar: " << bar.first << " " << bar.second << std::endl;

This produces:

foo: hello 5
bar: hello 5
foo: hello 7
bar: hello 5

So apparently a copy of foo has been created while the syntax suggests (to me at least) that the programmer wanted a reference to it. This violates the principle that a reference should be an alias to something. It would be great if somebody could explain what is going on and why.

(Note: I came across this here)

Upvotes: 13

Views: 718

Answers (2)

juanchopanza
juanchopanza

Reputation: 227538

The underlying types of foo and bar are different, so a temporary is created using an implicit conversion from the type on the RHS to the one on the LHS*. The C++ standard allows for a const reference to bind to a temporary and extend its lifetime.

The const reference bar binds to that temporary, which is a distinct object from foo.

If you were to use the same types, you'd get the result you expect:

std::pair<const std::string, int> foo("hello", 5);
const std::pair<const std::string, int> & bar = foo;

or

std::pair<std::string, int> foo("hello", 5);
const std::pair<std::string, int> & bar = foo;

would yield

foo: hello 5
bar: hello 5
foo: hello 7
bar: hello 7

*std::pair has a template constructor that allows this implicit conversion from one type of pair to another.

Upvotes: 15

That's a special property of references to const (and of rvalue refereneces, naturally). These references can bind to temporary objects.

Notice that std::pair<const std::string, int> (the type of foo) is a different type than std::pair<std::string, int> (the type to which bar wants to refer, modulo const). There is no object of type std::pair<std::string, int> in your code, so bar cannot bind to any such object.

However, as I said, references to const and rvalue references can bind to temporaries. And an object of type std::pair<std::string, int> can be implicitly created from an object of type std::pair<const std::string, int>. Therefore, such a temporary object is created, and bar is bound to that temporary. This reference binding also extends the lifetime of the temporary to that of bar*.

That's why you get a copy. If you changed the type of bar to std::pair<std::string, int> & (i.e. dropped the const), you'd instead get a compilation error that a non-const lvalue reference cannot bind to a temporary.


* In the special case of a member variable of reference type bound to a temporary, the temporary's lifetime will only extend until the end of the constructor which initialised the reference.

Upvotes: 5

Related Questions