Does guaranteed elision breaks bit_cast?

80 views
Skip to first unread message

Nicol Bolas

unread,
Dec 6, 2017, 2:43:31 PM12/6/17
to ISO C++ Standard - Future Proposals
The new `bit_cast` feature voted into C++20 allows you to do this:

int i = ...;
float j = bit_cast<float>(i);

Assuming the two types have the same size.

The concern is what happens with this:

int i = ...;
float *f = new(&i) auto(bit_cast<float>(i));

Well, the rules of guaranteed elision require that the prvalue returned by `bit_cast` directly initializes the object. And that object's storage is the same as the parameter passed to `bit_cast`.

So... how exactly does that work? Is this just something that is just for implementers to worry about? Or should there be standard wording to explicitly forbid this?

Myriachan

unread,
Dec 6, 2017, 3:52:58 PM12/6/17
to ISO C++ Standard - Future Proposals
As I understand it, eliding copy constructors is generally implemented by adding an invisible reference parameter to the target, so we could imagine that the above code gets translated into this C code:

float *bit_cast_float(const int *i, float *return_value)
{
    memmove
(return_value, i, sizeof(float));
   
return return_value;
}

int i = ...;
float *f = bit_cast_float(&i, (float *) &i);


Why would this break anything?

Melissa

Richard Smith

unread,
Dec 6, 2017, 4:21:02 PM12/6/17
to std-pr...@isocpp.org
Per [expr.new]p19, the invocation of the allocation function is sequenced before the evaluation of the initializer. So first we run the placement new operator. Once it returns, we begin to reuse its storage to hold a different object (the float object we're initializing), so by [basic.life]/1.3, the lifetime of the int object ends, and by [basic.life]/4, properties ascribed to that object (such as the object holding a particular value) cease to apply. The bit_cast happens too late.

Guaranteed copy elision might make it a bit more obvious that this could be a problem, but the lifetime and sequencing rules result in this being UB regardless of guaranteed copy elision. Here's another example where it's perhaps slightly more obvious that the code is broken:

struct X { int a, b; };
double d;
X *p = new (&d) X { 0, d };

Note that an implementation is permitted to store 0 to the first half of d before considering initializing the second half (and in particular, loading from d). This example has never performed a copy of an X object, and it wouldn't matter if it did -- the lifetime of d ends the moment that evaluation of the initializer begins.
Reply all
Reply to author
Forward
0 new messages