3

Is the following code legal in C++?

template<typename T>
class Foo {
public:
    Foo(T& v) : v_(v) {}

private:
    T& v_;
};

int a = 10;
Foo<int> f(a);

void Bar(int& a) {
    new (&f)Foo<int>(a);
}

References are not supposed to be bound twice, right?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
xiaofeng.li
  • 8,237
  • 2
  • 23
  • 30
  • 2
    You're not *binding* the reference `v_` twice, you *overwrite* it with the data from the object initialized by `Foo(a)` in `Bar`. – Some programmer dude Nov 04 '15 at 06:12
  • "References are not supposed to be bound twice, right?" – no, but what does that have to do with the rest of your question? – The Paramagnetic Croissant Nov 04 '15 at 06:13
  • @JoachimPileborg Thanks, now I look back and the last sentence doesn't make sense. References cannot be bound twice, it's not like we can do anything to rebind a reference. – xiaofeng.li Nov 04 '15 at 22:56
  • The code as present is legal, as long as you don't access the original object (e.g. through `f`) after that. – apple apple Mar 18 '22 at 16:24
  • @appleapple why would accessing the original object through `f` be invalid? I believe this is perfectly okay as or today's standard. Both original object and the newly constructed one by `Bar()` are *transparently replaceable*, which is the (met) requirement set by the standard. – Fureeish Aug 06 '23 at 20:22

2 Answers2

9

This is perfectly invalid.

[basic.life]/1, emphasis mine:

The lifetime of an object of type T ends when:

  • if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
  • the storage which the object occupies is reused or released.

The placement new reuses the storage, ending the lifetime of the object denoted by f.

[basic.life]/7:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
  • the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

Since the third bullet point is not satisfied, after a call to Bar, f does not refer to the object created by the placement new, but to the no-longer-living object previously there, and attempting to use it results in undefined behavior.

See also CWG1776 and P0137R0.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 1
    I don't understand something. In the placement new instruction : Does not the creation of the new object implies the reutilization of the storage which the object occupied ? In that case after the placement new, to me, the condition : "before the storage which the object occupied is reused or released" is not met, because future use of f happen after the storage is reused. I don't understand how theses rules can be put together. – Pumkko Nov 04 '15 at 10:33
  • Those bullet points [no longer exist in the standard](https://eel.is/c++draft/basic.life#8). – Fureeish Feb 25 '23 at 13:10
-1

It may be legal, but it's incredibly poor style. The argument to placement new is a void*, so you're telling C++ to reinterpret_cast the address of f as a void*, then using that as the location to construct something new - overwriting the original f.

Basically, don't do that.

cliffordheath
  • 2,536
  • 15
  • 16
  • Why not? It’s perfectly type-safe. – Davislor Nov 04 '15 at 06:21
  • Almost nothing in C++ is "perfectly type-safe" - C++ doesn't really know the meaning of this word. What if Foo was instantiated using a complex type? or even a string? The reinterpret_cast overwrites the constructed contents of the object. – cliffordheath Nov 04 '15 at 06:25
  • Okay, in virtually all real-world cases, you want the assignment operator here, which will properly destruct a complex type. That said, this is fine for plain old data or for an object that’s had its destructor called. In other words, it’s C++ letting you shoot yourself in the foot again. – Davislor Nov 04 '15 at 06:35
  • Even then the assignment operator is only ok if you have defined it correctly (or it's POD and you don't need one) - the default just overwrites the object. – cliffordheath Nov 04 '15 at 06:44
  • Anything will only work if defined correctly. Yes, the default assignment operator might fail on some badly-written classes. – Davislor Nov 04 '15 at 07:17