2

Why does the following code...

#include <iostream>
#include <map>

template< typename T, typename U >
class Map
{
public:
  Map( const T& t, const U& u ) { map_[ t ] = u; }
  Map< T, U >& operator() ( const T& t, const U& u )
  {
    map_[ t ] = u;
    return *this;
  }
  U& operator[] ( const T& t) { return map_[ t ]; } 

private:
  std::map< T, U > map_;
};

Map< int, std::string >& g_map = Map< int, std::string> ( 1, "lorem" )
                                                        ( 3, "ipsum" )
                                                        ( 5, "dolor" );

int main( int argc, char* argv[] )
{
  std::cout << g_map[3] << std::endl;
  return 0;
}

...produce this corrupted output?...

>g++ -g main.cpp
>./a.out
ipsumÿÿÿÿlorem!h€Ap€AD€A!ˆ€A¼gì¿P€A€A,€A!p€A€AY

I learned recently that assigning a reference to an anonymous rvalue extends the rvalue object's lifetime. So I thought that since the anonymous rvalue std::map is referred to by global-scope g_map, its lifetime would have been extended to that of a global-scope variable and it would have been valid to use g_map as any other global variable (where if not for the reference, the anonymous rvalue would have died at the closing semicolon).

Can someone please explain how lifetime extension rules apply to the above?

Compilation performed with gcc 4.9.2.

StoneThrow
  • 3,821
  • 11
  • 42
  • [Works for me](http://rextester.com/HTWI33012), for what it's worth. – Igor Tandetnik May 06 '17 at 03:43
  • Ah, I see what's going on. Your program exhibits undefined behavior: `g_map` is a dangling reference. – Igor Tandetnik May 06 '17 at 03:44
  • @StoneThrow MS VS of course, only that compiler allows temporary to be implicitly converted to lvalue – Slava May 06 '17 at 03:44
  • @IgorTandetnik - Can you explain/post an answer why `g_map` is a dangling reference? I don't understand. – StoneThrow May 06 '17 at 03:46
  • @Slava I thought so too - but it's subtler than that. No temporary is being bound to a reference, as far as the compiler is concerned; a call to `Map::operator()` masks away the "temporariness", by way of returning a reference to `*this`. – Igor Tandetnik May 06 '17 at 03:46

2 Answers2

4

You essentially have this:

class C {
public:
  C& detemporize() { return *this; }
};

C& cr = C().detemporize();

A temporary C instance is created. Then a method is called on it, which returns a C& reference. The compiler doesn't know nor care that the return value refers to that same temporary; for all it knows, it might very well return a reference to some global, long-lived object.

Anyway, cr ends up referring to that temporary, which then promptly dies, leaving cr dangling. Any subsequent attempt to use it exhibits undefined behavior.

In your code, Map::operator() plays the role of detemporize(), leaving g_map a dangling reference.

Igor Tandetnik
  • 45,980
  • 4
  • 51
  • 75
  • What you say makes sense, but I was told recently that assigning a reference to a temporary object "extends its lifetime." Now I'm unclear if that is true, and if true how long that extension is for. If there is such a thing as lifetime extension, I would have expected in my/your example for the temporary to "become global scope" because there is no "closing curly brace" for global vars, so I reasoned the variable's lifetime would have been "promoted" to the same as that of a real global variable (i.e. dies at program exit). – StoneThrow May 06 '17 at 03:56
  • You are not binding a temporary to a reference though - you are binding an lvalue. `const C& cr = C();` would have worked in the way you describe, extending the lifetime of the reference created by `C()`. – Igor Tandetnik May 06 '17 at 03:58
  • I'm a bit unclear why in my example you say I'm binding an _lvalue_ to a reference. My vocabulary may be primitive, but I think of an lvalue as a named variable "to the left of an equal sign", and I don't see that object in this picture. – StoneThrow May 06 '17 at 04:16
  • 1
    An lvalue can be a name of an object, or it could be a function call when that function returns a reference. E.g. `C global; C& getGlobal() { return global; }` Here, if you can write `global = x;` you can just as well write `getGlobal() = x;` – Igor Tandetnik May 06 '17 at 04:26
  • 1
    @StoneThrow Handy reading: [What are rvalues, lvalues, xvalues, glvalues, and prvalues?](http://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues) – user4581301 May 06 '17 at 04:30
2

I learned recently that assigning a reference to an anonymous rvalue extends the rvalue object's lifetime.

That only happens when you directly assign temporary object to a reference:

const obj &ref1 = obj(); // extends
const obj &ref = somefuncthatreturnsobj(); // extends

But there is no magic there, if you call a function that hides that reference somehow it does not work anymore:

class foo {
    const foo &get() const { return *this; };
};

const foo &ref1 = foo(); // extends lifetime of temporary
const foo &ref2 = foo().get(); // no lifetime extention, dangling reference
Slava
  • 40,641
  • 1
  • 38
  • 81
  • Is there anything in the C++ standard that explains/justifies this behavior? I feel like calling a function on a temporary is still another temporary...no? – StoneThrow May 06 '17 at 04:04
  • @StoneThrow It is more clear if you use the correct terminology for expression categories... the lifetime extension occurs when a reference is bound to a *prvalue* only. A function call to a function returning an lvalue reference is an *lvalue*. – M.M May 06 '17 at 04:26
  • 2
    @StoneThrow Consider: `class Map { static longLivedInstance; Map& operator()() { return longLivedInstance; } }; Map& ref = Map()();` Here, calling a function on a temporary returns a reference to something other than that temporary; while the signature of the method is exactly the same as when it `return *this;`. You essentially expect the compiler to guess what you mean by examining the actual implementation of the function. Which wouldn't even help - imagine a method that does `return rand() % 2 ? longLivedInstance : *this;` – Igor Tandetnik May 06 '17 at 12:39