0

The 2 print statements print different numbers. As far as I can see I'm not doing any dodgy const_cast here so I'm not sure what UB I could have possibly committed.

  1. Is this code well-formed?

  2. Can the compiler rely on the fact that A::num is const so it's allowed to print the same number ?

Code:

struct A
{
    const int num = 100;

    A() {}    

    A(int in) : num{in} {}

    void call()
    {
        new (this) A{69};
    }
};

int main()
{
    A a;    
    std::cout << a.num << '\n';
    a.call();
    std::cout << a.num << '\n';
}
melpomene
  • 84,125
  • 8
  • 85
  • 148
PoweredByRice
  • 2,479
  • 1
  • 20
  • 26
  • What is the output, and what do you expect the output to be? – John Ilacqua Nov 10 '18 at 02:15
  • 2
    I'm going to go out on a limb and say that it is almost certainly undefined behavior to `placement new` on memory that already contains an existing class instance. You likely get away with it (though I'd assume the compiler warns you), because no memory allocations or virtual functions are involved, but it seems like the dodgiest of dodgy ideas. – ShadowRanger Nov 10 '18 at 02:16
  • I'd suspect that any undefined behaviour is more likely to be related to the `const`ness of `num` than being because the memory already contains an existing class instance, since that class's destructor will do nothing anyway. – John Ilacqua Nov 10 '18 at 02:25
  • 4
    **[basic.life]** "Creating a new object within the storage that a const complete object with static, thread, or automatic storage duration occupies, or within the storage that such a const object used to occupy before its lifetime ended, results in undefined behavior." – Raymond Chen Nov 10 '18 at 03:46
  • 1
    @ShadowRanger Believe it or not, you are permitted to reuse the storage of a live object! **[basic.life]** "A program may end the lifetime of an object by reusing the storage which the object occupies or by explicitly calling the destructor for an object with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released..." – Raymond Chen Nov 10 '18 at 03:50
  • 2
    @RaymondChen Is that true, even if only the member is const? "complete object" is defined in [intro.object] as "An object that is not a subobject of any other object is called a complete object." –  Nov 10 '18 at 04:09
  • 1
    @RaymondChen `a` not `const`, so the placement new is not UB. – Rakete1111 Nov 10 '18 at 05:40
  • @ShadowRanger "_certainly undefined behavior to placement new on memory that already contains an existing class instance_" no it's perfectly legal in C++ to do anything on the memory of existing objects (except some global const objects); but that destroys these preexisting objects, they don't exist as objects anymore "_because no memory allocations or virtual functions are involved_" virtual functions have no relevance here, but trying to use an object that doesn't exist (because you erased it), does – curiousguy Nov 10 '18 at 06:28
  • "_I'm not doing any dodgy const_cast_" In any language that permits low level programming, with the direct use of pointer to memory, and manual memory management you are going to have possible undefined behavior without a pointer cast, or other "dodgy" construct. **It's a direct consequence of trusting the programmer with low level memory and lifetime management.** Without it, C++ wouldn't be C++. – curiousguy Nov 10 '18 at 06:32

1 Answers1

4

No, your code has UB. Remove the const on num and you don't get any UB anymore.

The problem is that the standard provides a guarantee that a const object doesn't change. But if you reuse the same storage, then you can "modify" the const object in a way.

[basic.life]p8 explicitly prohibits this by saying that the old name of the object only refers to the new object under certain conditions. One of them is that your class doesn't have any const members. So by extension, your second a.num is UB, as the a refers to the old destructed object.

However, there are two ways to avoid this UB. First, you can store the pointer to the new object:

struct A *new_ptr;
struct A {
  // [...]
  void call() {
      new_ptr = new (this) A{69};
  }
};

int main()
{
    A a;    
    std::cout << a.num << '\n';
    a.call();
    std::cout << new_ptr->num << '\n'; // ok
}

Or use std::launder:

std::cout << std::launder(&a)->num << '\n'; // second access
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • 1
    Yes. But note that under the theory (postulated by the std) that pointers are PODs (or trivial types), **the use of `std::launder` is inherently redundant** as all trivial types of a given type and a given byte wise representation must have the same intentional value, not just the same numerical value. Or trivial type is really a worthless concept. – curiousguy Nov 10 '18 at 06:35