I am exploring the possibility of implementing true (partially) immutable data structures in C++. As C++ does not seem to distinguish between a variable and the object that variable stores, the only way to truly replace the object (without assignment operation!) is to use placement new:
auto var = Immutable(state0);
// the following is illegal as it requires assignment to
// an immutable object
var = Immutable(state1);
// however, the following would work as it constructs a new object
// in place of the old one
new (&var) Immutable(state1);
Assuming that there is no non-trivial destructor to run, is this legal in C++ or should I expect undefined behaviour? If its standard-dependant, which is the minimal/maximal standard version where I can expect this to work?
Addendum: since it seems people still read this in 2019, a quick note — this pattern is actually legally possible in modern (post 17) C++ using std::launder().
What you wrote is technically legal but almost certainly useless.
Suppose
struct Immutable {
const int x;
Immutable(int val):x(val) {}
};
for our really simple immutable type.
auto var = Immutable(0);
::new (&var) Immutable(1);
this is perfectly legal.
And useless, because you cannot use var to refer to the state of the Immutable(1) you stored within it after the placement new. Any such access is undefined behavior.
You can do this:
auto var = Immutable(0);
auto* pvar1 = ::new (&var) Immutable(1);
and access to *pvar1 is legal. You can even do:
auto var = Immutable(0);
auto& var1 = *(::new (&var) Immutable(1));
but under no circumstance may you ever refer to var after you placement new'd over it.
Actual const data in C++ is a promise to the compiler that you'll never, ever change the value. This is in comparison to references to const or pointers to const, which is just a suggestion that you won't modify the data.
Members of structures declared const are "actually const". The compiler will presume they are never modified, and won't bother to prove it.
You creating a new instance in the spot where an old one was in effect violates this assumption.
You are permitted to do this, but you cannot use the old names or pointers to refer to it. C++ lets you shoot yourself in the foot. Go right ahead, we dare you.
This is why this technique is legal, but almost completely useless. A good optimizer with static single assignment already knows that you would stop using var at that point, and creating
auto var1 = Immutable(1);
it could very well reuse the storage.
Caling placement new on top of another variable is usually defined behaviour. It is usually a bad idea, and it is fragile.
Doing so ends the lifetime of the old object without calling the destructor. References and pointers to and the name of the old object refer to the new one if some specific assumptions hold (exact same type, no const problems).
Modifying data declared const, or a class containing const fields, results in undefined behaviour at the drop of a pin. This includes ending the lifetime of an automatic storage field declared const and creating a new object at that location. The old names and pointers and references are not safe to use.
[Basic.life 3.8]/8:
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:
(8.1)
the storage for the new object exactly overlays the storage location which the original object occupied,
and
(8.2)
the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
(8.3)
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
(8.4)
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).
In short, if your immutability is encoded via const members, using the old name or pointers to the old content is undefined behavior.
You may use the return value of placement new to refer to the new object, and nothing else.
Exception possibilities make it extremely difficult to prevent code that exdcutes undefined behaviour or has to summarially exit.
If you want reference semantics, either use a smart pointer to a const object or an optional const object. Both handle object lifetime. The first requires heap allocation but permits move (and possibly shared references), the second permits automatic storage. Both move manual object lifetime management out of business logic. Now, both are nullable, but avoiding that robustly is difficult doing it manually anyhow.
Also consider copy on write pointers that permit logically const data with mutation for efficiency purposes.
From the C++ standard draft N4296:
3.8 Object lifetime
[...]
The lifetime of an object of type T ends when:
(1.3) — if T is a class
type with a non-trivial destructor (12.4), the destructor call starts,
or
(1.4) — the storage which the object occupies is reused or
released.
[...]
4 A program may end the lifetime of any object by
reusing the storage which the object occupies or by explicitly calling
the destructor for an object of a class type 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; however, if there is no explicit call to the destructor or
if a delete-expression (5.3.5) is not used to release the storage, the
destructor shall not be implicitly called and any program that depends
on the side effects produced by the destructor has undefined behavior.
So yes, you can end the lifetime of an object by reusing its memory, even of one with non-trivial destructor, as long as you don't depend on the side effects of the destructor call.
This applies when you have non-const instances of objects like struct ImmutableBounds { const void* start; const void* end; }
You've actually asked 3 different questions :)
1. The contract of immutability
It's just that - a contract, not a language construct.
In Java for instance, instances of String class are immutable. But that means that all methods of the class have been designed to return new instances of class rather than modifying the instance.
So if you would like to make Java's String into a mutable object, you couldn't, without having access to its source code.
Same applies to classes written in C++, or any other language. You have an option to create a wrapper (or use a Proxy pattern), but that's it.
2. Using placement constructor and allocating into an initialized are off memory.
That's actually what they were created to do in the first place.
The most common use case for the placement constructor are memory pools - you preallocate a large memory buffer, and then you allocate your stuff into it.
So yes - it is legal, and nobody won't mind.
3. Overwriting class instance's contents using a placement allocator.
Don't do that.
There's a special construct that handles this type of operation, and it's called a copy constructor.
Related
Consider the following fragment (assume that T is trivially constructible and trivially destructible):
std::optional<T> opt;
opt.emplace();
T& ref = opt.value();
opt.emplace();
// is ref guaranteed to be valid here?
From the definition of std::optional we know that the contained instance is guaranteed to be allocated inside the std::optional container, hence we know that the reference ref will always be referring to the same memory location. Are there circumstances where said reference will not retain validity after the pointed-to object is destroyed and then constructed again?
C++20 has the following rule, [basic.life]/8:
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 original object is transparently replaceable (see below) by the new object. An object o1 is transparently replaceable by an object o2 if:
the storage that o2 occupies exactly overlays the storage that o1 occupied, and
o1 and o2 are of the same type (ignoring the top-level cv-qualifiers), and
o1 is not a complete const object, and
neither o1 nor o2 is a potentially-overlapping subobject (6.7.2), and
either o1 and o2 are both complete objects, or o1 and o2 are direct subobjects of objects p1 and p2 ,
respectively, and p1 is transparently replaceable by p2.
This suggests that as long as T is not const-qualified, destroying the T inside an std::optional<T> and then recreating it should result in a reference to the old object automatically referring to the new object. As pointed out in the comments section, this is a change from the old behaviour, abolishing a requirement that T must not contain a non-static data member of const-qualified or reference type. (Edit: I previously asserted that the change was made retroactively, as I confused it with a different change in C++20. I am not sure whether the resolution to RU 007 and US 042 as indicated in N4858 were made retroactive, but I suspect the answer is yes, because the change was needed to fix code involving standard library templates that was probably not intended to be broken from C++11 through C++17.)
However, we are making the assumption that the new T object is being created "before the storage which the [old] object occupied is reused or released". If I were writing an "adversarial" implementation of the standard library, I could set it up so that the emplace call reuses the underlying storage prior to creating the new T object. This would prevent the old T object from being transparently replaced by the new one.
How might an implementation "reuse" the storage? Typically, the underlying storage might be declared like this:
union {
char no_object;
T object;
};
When the default constructor of optional is called, no_object is initialized (the value does not matter)1. An emplace() call checks whether there is a T object or not (by checking a flag that is not shown here). If a T object is present, then object.~T() is called. Finally, something similar to construct_at(addressof(object)) is called in order to construct the new T object.
Not that any implementation would ever do this, but you could imagine an implementation that, in between the calls to object.~T() and construct_at(addressof(object)), re-initializes the no_object member. This would be a "reuse" of the storage that was previously occupied by object. This would imply that the requirements of [basic.life]/8 are not met.
Of course, the practical answer to your question is that (1) there is no reason for an implementation to do something like this, and (2) even if an implementation did it, the developers would ensure that your code still behaves as if the T object was transparently replaced. Your code is reasonable under the assumption that the standard library implementation is reasonable, and compiler developers do not like to break code with that property, since doing so would needlessly aggravate their users.
But if a compiler developer were inclined to break your code (based on the argument that the more undefined behaviour there is, the more the compiler can optimize) then they could break your code even without changing the <optional> header file. The user is required to treat the standard library like a "black box" that only guarantees what the standard explicitly guarantees. So under a pedantic reading of the standard, it's unspecified whether or not attempting to access ref after the second emplace call has undefined behaviour. If it's unspecified whether it's UB, then the compiler is allowed to start treating it as UB whenever it wants.
1 The reason for this is historical; C++17 requires that a constexpr constructor initialize exactly one variant member of a union. This rule was abolished in C++20, so a C++20 implementation could omit the no_object member.
In the following short example, what can be said about the object the pointer f points to or used to point to just before returning from main?
#include <vector>
struct foo {
std::vector<int> m;
};
int main()
{
auto f = new foo;
f->~foo();
}
I believe that there is no longer an object foo where f used to point. I've received a lot of comments that this may not be correct and that instead there could be an object foo in a destroyed, dead or otherwise invalid state.
What does the language standard have to say about the existence of an objects that is explicitly destroyed but whose storage is still valid?
In other words, can it reasonably be said that there is still an object at f that is outside of its lifetime? Is there such a thing as an object that is not in its lifetime, not begin constructed and not being destructed?
Edit :
It is clear that an object can exist when it isn't in its lifetime. During construction and destruction there is an object and its lifetime has not yet begun or as already ended. From https://timsong-cpp.github.io/cppwp/intro.object#1 :
[...] An object occupies a region of storage in its period of construction ([class.cdtor]), throughout its lifetime, and in its period of destruction ([class.cdtor]). [...]
But after f->~foo(); the object that was pointed to by f (lets call it o) is not being constructed, it is not in its lifetime and it is not being destructed. My reading of this section is that o cannot occupy the storage anymore because it isn't in any of the enumerated situations. It seems like this implies that there is no o anymore and that there can't be a pointer to o anymore. By contradiction, if you had a pointer to o then that pointer would point to storage which o can't occupy.
Edit 2 :
If there isn't an object anymore, then what kind of value does foo have? It seems like the only sensible possible value it can have is a pointer to an object, which would contradict the statement. See this question.
In C++, objects essentially are eternal. There's nothing in the language that makes an object disappear. An object that is outside of its lifetime is still an object, it still occupies storage, and the standard has specific things that you can do with a pointer/reference to an object which is outside of its lifetime.
An object only truly goes away when it is impossible to have a valid pointer/reference to it. This happens when the storage occupied by that object ends its storage duration. A pointer to storage that is past its duration is an invalid pointer, even if the address itself later becomes valid again.
So by calling the destructor instead of using delete f (which would also deallocate the storage), f remains pointing to an object of type foo, but that object is outside of its lifetime.
The justification for my above statements basically boils down to the standard having none of the provisions that it would need in order to support the concept of objects being uncreated.
Where is object uncreation?
The standard provides clear, unequivocal statements about when an object comes to exist within a piece of storage. [intro.object]/1 outlines the exact mechanisms that provoke the creation of an object.
The standard provides clear, unequivocal statements about when an object's lifetime begins and ends. [basic.life] in its entirely outlines these things, but [basic.life]/1 in particular explains when an object's lifetime begins and ends.
The standard does not provide any statement (clear or otherwise) about when an object no longer exists. The standard says when objects are created, when their lifetimes begin, and when they end. But never does it say when they stop existing within a piece of storage.
There has also been discussion about statements of the form:
any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways.
Emphasis added.
The use of the past-tense suggests that the object is no longer located in that storage. But when did the object stop being located there? There is no clear statement about what exactly caused that to happen. And without that, the use of past-tense here just doesn't matter.
If you can't point to a statement about when it stopped being there, then the absolute most you can say is that there are a couple of places in the standard with wording that could be cleaned up. It doesn't undo the clear fact that the standard does not say when objects stop existing.
Pointer validity
But it does say when objects are no longer accessible.
In order for an object to cease existing, the standard would have to account for pointers which point to those objects when they no longer exist. After all, if a pointer is pointing to an object, then that object must still exist, right?
[basic.compound]/3 outlines the states that a pointer can have. Pointers can be in one of four states:
a pointer to an object or function (the pointer is said to point to the object or function), or
a pointer past the end of an object ([expr.add]), or
the null pointer value ([conv.ptr]) for that type, or
an invalid pointer value.
There is no allowance given for a pointer which points to no object. There is an allowance for an "invalid pointer value", but pointers only become invalid when the storage duration for the storage they point into ends:
When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values.
Note that this statement means that all pointers to such objects cease being in the "pointer to object" state and enter the "invalid pointer" state. Thus, objects within such storage (both within and outside of their lifetimes) stop being accessible.
This is exactly the sort of statement that would need to exist for the standard to support the concept of objects no longer existing.
But no such statement exists.
[basic.life] does have several statements that address limited ways that pointers to objects outside of their lifetime can be used. But note the specific wording it uses:
For an object under construction or destruction, see [class.cdtor]. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.deallocation]), and using the pointer as if the pointer were of type void*, is well-defined.
It never says that the pointer "points to" allocated storage. It never undoes [basic.compound]/3's declaration about the kinds of pointers. The pointer is still a pointer to an object; it's just that the pointer "refers to allocated storage". And that the pointer can be used as a void*.
That is, there's no such thing as a "pointer to allocated storage". There is a "pointer to an object outside of its lifetime, whose pointer value can be used to refers to allocated storage". But is still a "pointer to an object".
Lifetime is not existence
Objects must exist in order to have a lifetime. The standard makes that clear. However, the standard does not at any point link the existence of an object to its lifetime.
Indeed, the object model would be a lot less complicated if ending the lifetime of an object meant that the object didn't exist. Most of [basic.life] is about carving out specific ways you can use the name of an object or a pointer/reference to it outside of the lifetime of that object. We wouldn't need that sort of stuff if the object itself didn't exist.
Stated in discussion about this matter was this:
I believe mentions of out-of-lifetime objects are there to account for objects that are being constructed and objects that are being destructed.
If that were true, what is [basic.life]/8 talking about with this statement:
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
If pointers to the original object become pointers to allocated memory when the object's lifetime ends, why does this statement talk about pointers to the original object? Pointers can't point to objects that don't exist because they don't exist.
This passage can only make sense if those objects continue to exist outside of their lifetimes. And no, it's not just about within the constructor/destructor; the example in the section makes that abundantly clear:
struct C {
int i;
void f();
const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
C c1;
C c2;
c1 = c2; // well-defined
c1.f(); // well-defined; c1 refers to a new object of type C
While operator= does call the destructor, that destructor finishes before the this pointer is used. Thus, the special provisions of of [class.cdtor] does not apply to this at the moment the new object is created. So the new object is created outside of the destructor call to the old one.
So it's very clear that the "outside its lifetime" rules for objects are meant to always work. It's not just a provision for constructors/destructors (if it was, it would explicitly call that out). This means that names/pointers/references must still name/point-to/reference objects outside of their lifetime until the creation of the new object.
And for that to happen, the object they name/point-to/reference must still exist.
I know that this question was asked several times already but I couldn't find an answer for this particular case.
Let's say I have a trivial class that doesn't own any resources and has empty destructor and default constructor. It has a handful of member variables with in-class initialization; not one of them is const.
I want to re-initialize and object of such class it without writing deInit method by hand. Is it safe to do it like this?
void A::deInit()
{
new (this)A{};
}
I can't see any problem with it - object is always in valid state, this still points to the same address; but it's C++ so I want to be sure.
Similarly to the legality of delete this, placement new to this is also allowed as far as I know. Also, regarding whether this, or other pre-existing pointers / references can be used afterwards, there are a few restrictions:
[basic.life]
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
neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).
The first two are satisfied in this example, but the last two will need to be taken into consideration.
Regarding the third point, given that the function is non-const-qualified, it should be fairly safe to assume that the original object is non-const. The fault is on the caller side if the constness has been cast away. Regarding const / reference member, I think that can be checked by asserting that this is assignable:
static_assert(std::is_trivial_v<A> && std::is_copy_assignable_v<A>);
Of course, since assignability is a requirement, you could instead simply use *this = {}; which I would expect to produce the same program. A perhaps more interesting use case might be to reuse memory of *this for an object of another type (which would fail the requirements for using this, at least without reinterpreting + laundering).
Similar to delete this, placement new to this could hardly be described as "safe".
The rules that cover this are in [basic.life]/5
A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type. For an object of a class type, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression is not used to release the storage, the destructor is not implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.
and [basic.life]/8
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
neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).
Since your object is trivial you don't have to worry about [basic.life]/5 and as long as you satisfy the bullet points from [basic.life]/8, then it is safe.
In the comments and answers to this question:
Virtual function compiler optimization c++
it is argued that a virtual function call in a loop cannot be devirtualized, because the virtual function might replace this by another object using placement new, e.g.:
void A::foo() { // virtual
static_assert(sizeof(A) == sizeof(Derived));
new(this) Derived;
}
The example is from a LLVM blog article about devirtualization
Now my question is: is that allowed by the standard?
I could find this on cppreference about storage reuse: (emphasis mine)
A program is not required to call the destructor of an object to end its lifetime if the object is trivially-destructible or if the program does not rely on the side effects of the destructor. However, if a program ends the lifetime of an non-trivial object, it must ensure that a new object of the same type is constructed in-place (e.g. via placement new) before the destructor may be called implicitly
If the new object must have the same type, it must have the same virtual functions. So it is not possible to have a different virtual function, and thus, devirtualization is acceptable.
Or do I misunderstand something?
The quote you provided says:
If a program ends the lifetime of an non-trivial object, it must ensure that a new object of the same type is constructed in-place (e.g. via placement new) before the destructor may be called implicitly
The intent of this statement relates to something a bit different to what you are doing. The statement is meant to say that when you destroy an object without destroying its name, something still refers to that storage with the original type, o you need to construct a new object there so that when the implicit destruction occurs, there is a valid object to destroy. This is relevant for example if you have an automatic ("stack") variable, and you call its destructor--you need to construct a new instance there before the destructor is called when the variable goes out of scope.
The statement as a whole, and its "of the same type" clause in particular, has no bearing on the topic you're discussing, which is whether you are allowed to construct a different polymorphic type having the same storage requirements in place of an old one. I don't know of any reason why you shouldn't be allowed to do that.
Now, that being said, the question you linked to is doing something different: it is calling a function using implicit this in a loop, and the question is whether the compiler could assume that the vptr for this will not change in that loop. I believe the compiler could (and clang -fstrict-vtable-pointers does) assume this, because this is only valid if the type is the same after the placement new.
So while the quotes from the standard you have provided are not relevant to this issue, the end result is that it does seem possible for an optimizer to devirtualize function calls made in a loop under the assumption that the type of *this (or its vptr) cannot change. The type of an object stored at an address (and its vptr) can change, but if it does, the old this is no longer valid.
It appears that you intend to use the new object using handles (pointers, references, or the original variable name) that existed prior to its recreation. That's allowed only if the instance type is not changed, plus some other conditions excluding const objects and sub-objects:
From [basic.life]:
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 of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).
Your quote from the Standard is merely a consequence of this one.
Your proposed "devirtualization counter-example" does not meet these requirements, therefore all attempts to access the object after it is replaced will cause undefined behavior.
The blog post even pointed this out, in the very next sentence after the example code you looked at.
I've seen some examples of placement new, and am a little confused as to what's happening internally with the various types.
A simple example:
using my_type = std::string;
using buffer_type = char;
buffer_type buffer[1000];
my_type* p{ new (buffer) my_type() };
p->~my_type();
From what I understand, this is valid, though I'm concerned about what happens to the char array of buffer[]. It seems like this is ok to do as long as I don't access the variable buffer in any form after creating a new object in place on its memory.
For the sake of keeping this simple, I'm not concerned about anything to do with proper alignment here, or any other topics of what can go wrong when calling placement new other than: what happens to the original type? Could I use another type such as buffer_type = int to achieve a similar effect (ignoring the possibility of how much memory that will actually take)? Is any POD safe as buffer_type? How about non-POD types? Do I have to tell the compiler that the char array is no longer valid in some way? Are there restrictions on what my_type could be or not be here?
Does this code do what I expect it to, is it well defined, and are there any minor modifications that would either keep this as well defined or break it into undefined behaviour?
what happens to the original type?
You mean the original object? It get's destroyed, that is, its lifetime ends. The lifetime of the array object of type buffer_type [1000] ends as soon as you reuse its storage.
A program may end the lifetime of any object by reusing the storage
which the object occupies or by explicitly calling the destructor for
an object of a class type 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; however, if there is no
explicit call to the destructor or if a delete-expression (5.3.5) is
not used to release the storage, the destructor shall not be
implicitly called and any program that depends on the side effects
produced by the destructor has undefined behavior.
Note that this implies that we should not use something with a non-trivial destructor for the buffer: The elements' destructors are called at the end of the scope it's defined in. For e.g. std::string as the element type that would imply a destructor call on an non-existing array subobject, which clearly triggers undefined behavior.
If a program ends the lifetime of an object of type T with […]
automatic (3.7.3) storage duration and if T has a non-trivial
destructor, the program must ensure that an object of the original
type occupies that same storage location when the implicit destructor
call takes place; otherwise the behavior of the program is undefined.
To avoid that you would have to construct std:strings into that buffer after you're done with it, which really seems nonsensical.
Is any POD safe as buffer_type?
We do not necessarily need PODs - they have a lot of requirements that are not a necessity here.
The only things that bother us are the constructor and the destructor.
It matters whether the types destructor is trivial (or for an array, the arrays element types' destructor). Also it's feasible that the constructor is trivial, too.
POD types feel safer since they suffice both requirements and convey the idea of "bare storage" very well.
Are there restrictions on what my_type could be or not be here?
Yes. It shall be an object type. It can be any object type, but it cannot be a reference.
Any POD type would be safe. Also, while a bit dangerous, I believe most non-POD types whose destructor is empty would work here as well. If the destructor is not empty, it will be called on buffer and try to access its data which is no longer vaild (due to the placement new).