4

After seeing that a local reference-to-const may prolong the life of a temporary, I encountered the need to conditionally bind a local reference-to-const to either a function parameter or the temporary result of a function call, i.e.:

class Gizmo
{
    // Rule of Five members implemented
};

Gizmo Frobnicate(const Gizmo& arg);

void ProcessGizmo(const Gizmo& arg, bool frobnicate)
{
    const Foo& local = frobnicate ? Frobnicate(arg) : arg;
    // Perform some work on local
}

A practical example: the boolean specifies whether to compress a buffer and you'd like to write uniform code that operates on local either way.

The above example, however, invoked Gizmo's copy-constructor on arg when frobnicate was false. I managed to avoid the invocation of the copy-constructor by changing Frobnicate(arg) to static_cast<const Gizmo&>(Frobnicate(arg)).

My question becomes: how does the ternary operator interact with the rule about binding a local reference-to-const to a temporary? Is my solution legal and well-behaved?

R Sahu
  • 200,579
  • 13
  • 144
  • 260
DoomMuffins
  • 1,144
  • 1
  • 8
  • 18

1 Answers1

5

No, it's not well-behaved. For the lifetime extension to occur, the temporary must be bound directly to the reference. Once you add a cast, the binding is no longer direct, and the resulting reference becomes dangling.

We can see this with some simple testing code:

#include <iostream>
struct Gizmo
{
    ~Gizmo() { std::cout << "Gizmo destroyed\n"; }
};

Gizmo Frobnicate(const Gizmo& arg) { return arg; }

void ProcessGizmo(const Gizmo& arg, bool frobnicate)
{
    const Gizmo& local = frobnicate ? static_cast<const Gizmo&>(Frobnicate(arg)) : arg;
    // Perform some work on local
    (void) local;
    std::cout << "Processing\n";
}

int main(){
    Gizmo g;
    ProcessGizmo(g, true);
    std::cout << "Processed\n";
}

This prints:

Gizmo destroyed
Processing
Processed
Gizmo destroyed

That first Gizmo destroyed message is from the return value of Frobnicate(); it's being destroyed at the end of that line - without lifetime extension.

An obvious workaround is moving the processing into another function:

void DoProcessGizmo(const Gizmo& arg) { /* process the Gizmo */ }

void ProcessGizmo(const Gizmo& arg, bool frobnicate)
{
    return frobnicate ? DoProcessGizmo(Frobnicate(arg)) : DoProcessGizmo(arg);
}
T.C.
  • 129,563
  • 16
  • 274
  • 404
  • What about "return DoProcessGizmo( frobnicate ? Frobnicate(arg) : arg );" This also seems to work as expected – dats Oct 02 '18 at 14:43
  • That makes a copy that the OP is seeking to avoid. – T.C. Oct 02 '18 at 20:20
  • Hum... you are right! But I'm pretty sure I've seen that copy avoided in some version of MSVC in the past... Not that I can reproduce it with a recent version though. Thanks! I have to go and avoid some copies now! – dats Oct 04 '18 at 08:56
  • What about `return DoProcessGizmo( frobnicate ? static_cast< const Gizmo & >( Frobnicate( arg ) ) : arg );`? I assume this is legal C++, with no undefined behavior (albeit uglier than having two independent calls to `DoProcessGizmo`). – jchl Jul 31 '20 at 11:18