13

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)

Sarien
  • 6,647
  • 6
  • 35
  • 55

2 Answers2

15

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.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 1
    And in which cases will assigning incompatible types create a temporary as opposed to just throwing a compiler error? – Sarien Jan 26 '15 at 13:23
  • @Sarien There needs to be a valid conversion between `T1` and `T2` in `const T2& = T1`; – juanchopanza Jan 26 '15 at 13:25
  • @Sarien the compiler looks for a viable conversion. It can invoke the constructor of `pair` with copies of the value in the original pair, so it will do. – The Paramagnetic Croissant Jan 26 '15 at 13:25
  • @juanchopanza Okay, so different types will lead to copies when a valid conversion exists and to an error otherwise. Good. Now I see, that my case will lead to a copy because it compiles and the types are different. Is there anything I should know from memory about when these conversions exist? – Sarien Jan 26 '15 at 13:33
  • @Sarien I think you need to remember that if the types are different you need an *implicit* conversion. If there isn't one, you'll get a compiler error. Then, you should know the classes you are using. For example, this [`std::pair` constructor](http://en.cppreference.com/w/cpp/utility/pair/pair) shows a template constructor that allows for the implicit conversion from a different type of pair. – juanchopanza Jan 26 '15 at 13:36
  • @Sarien Note also that your code wouldn't have compiled if the reference variable `bar` was not `const`. Non-const references are forbidden to bind to unnamed temporaries [for very similar reasons](http://stackoverflow.com/questions/8763398/why-is-it-illegal-to-take-the-address-of-an-rvalue-temporary/9779765#9779765). – ComicSansMS Jan 26 '15 at 16:26
  • 2
    To emphasis the example for `bar` to be the same type, you could use `const auto & bar = foo;`. That's a purpose of the keyword, to prevent unwanted implicit conversion. – galop1n Jan 26 '15 at 16:57
5

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.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455