"constructing" a trivially-copyable object with memcpy - c++

In C++, is this code correct?
#include <cstdlib>
#include <cstring>
struct T // trivially copyable type
{
int x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T a{};
std::memcpy(buf, &a, sizeof a);
T *b = static_cast<T *>(buf);
b->x = b->y;
free(buf);
}
In other words, is *b an object whose lifetime has begun? (If so, when did it begin exactly?)

This is unspecified which is supported by N3751: Object Lifetime, Low-level Programming, and
memcpy which says amongst other things:
The C++ standards is currently silent on whether the use of memcpy to
copy object representation bytes is conceptually an assignment or an
object construction. The difference does matter for semantics-based
program analysis and transformation tools, as well as optimizers,
tracking object lifetime. This paper suggests that
uses of memcpy to copy the bytes of two distinct objects of two different trivial copyable tables (but otherwise of the same size) be
allowed
such uses are recognized as initialization, or more generally as (conceptually) object construction.
Recognition as object construction will support binary IO, while still
permitting lifetime-based analyses and optimizers.
I can not find any meeting minutes that has this paper discussed, so it seems like it is still an open issue.
The C++14 draft standard currently says in 1.8 [intro.object]:
[...]An object is created by a definition (3.1), by a new-expression
(5.3.4) or by the implementation (12.2) when needed.[...]
which we don't have with the malloc and the cases covered in the standard for copying trivial copyable types seem to only refer to already existing objects in section 3.9 [basic.types]:
For any object (other than a base-class subobject) of trivially
copyable type T, whether or not the object holds a valid value of type
T, the underlying bytes (1.7) making up the object can be copied into
an array of char or unsigned char.42 If the content of the array of
char or unsigned char is copied back into the object, the object shall
subsequently hold its original value[...]
and:
For any trivially copyable type T, if two pointers to T point to
distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a
base-class subobject, if the underlying bytes (1.7) making up obj1 are
copied into obj2,43 obj2 shall subsequently hold the same value as
obj1.[...]
which is basically what the proposal says, so that should not be surprising.
dyp points out a fascinating discussion on this topic from the ub mailing list: [ub] Type punning to avoid copying.
Propoal p0593: Implicit creation of objects for low-level object manipulation
The proposal p0593 attempts to solve this issues but AFAIK has not been reviewed yet.
This paper proposes that objects of sufficiently trivial types be created on-demand as necessary within newly-allocated storage to give programs defined behavior.
It has some motivating examples which are similar in nature including a current std::vector implementation which currently has undefined behavior.
It proposes the following ways to implicitly create an object:
We propose that at minimum the following operations be specified as implicitly creating objects:
Creation of an array of char, unsigned char, or std::byte implicitly creates objects within that array.
A call to malloc, calloc, realloc, or any function named operator new or operator new[] implicitly creates objects in its returned storage.
std::allocator::allocate likewise implicitly creates objects in its returned storage; the allocator requirements should require other allocator implementations to do the same.
A call to memmove behaves as if it
copies the source storage to a temporary area
implicitly creates objects in the destination storage, and then
copies the temporary storage to the destination storage.
This permits memmove to preserve the types of trivially-copyable objects, or to be used to reinterpret a byte representation of one object as that of another object.
A call to memcpy behaves the same as a call to memmove except that it introduces an overlap restriction between the source and destination.
A class member access that nominates a union member triggers implicit object creation within the storage occupied by the union member. Note that this is not an entirely new rule: this permission already existed in [P0137R1] for cases where the member access is on the left side of an assignment, but is now generalized as part of this new framework. As explained below, this does not permit type punning through unions; rather, it merely permits the active union member to be changed by a class member access expression.
A new barrier operation (distinct from std::launder, which does not create objects) should be introduced to the standard library, with semantics equivalent to a memmove with the same source and destination storage. As a strawman, we suggest:
// Requires: [start, (char*)start + length) denotes a region of allocated
// storage that is a subset of the region of storage reachable through start.
// Effects: implicitly creates objects within the denoted region.
void std::bless(void *start, size_t length);
In addition to the above, an implementation-defined set of non-stasndard memory allocation and mapping functions, such as mmap on POSIX systems and VirtualAlloc on Windows systems, should be specified as implicitly creating objects.
Note that a pointer reinterpret_cast is not considered sufficient to trigger implicit object creation.

The code is legal now, and retroactively since C++98!
The answer by #Shafik Yaghmour is thorough and relates to the code validity as an open issue - which was the case when answered. Shafik's answer correctly refer to p0593 which at the time of the answer was a proposal. But since then, the proposal was accepted and things got defined.
Some History
The possibility of creating an object using malloc was not mentioned in the C++ specification before C++20, see for example C++17 spec [intro.object]:
The constructs in a C++ program create, destroy, refer to, access, and manipulate
objects. An object is created by a definition (6.1), by a new-expression (8.5.2.4),
when implicitly changing the active member of a union (12.3), or when a temporary
object is created (7.4, 15.2).
Above wording does not refer to malloc as an option for creating an object, thus making it a de-facto undefined behavior.
It was then viewed as a problem, and this issue was addressed later by https://wg21.link/P0593R6 and accepted as a DR against all C++ versions since C++98 inclusive, then added into the C++20 spec, with the new wording:
[intro.object]
The constructs in a C++ program create, destroy, refer to, access, and manipulate objects. An object is created by a definition, by a new-expression, by an operation that implicitly creates objects (see below)...
...
Further, after implicitly creating objects within a specified region of
storage, some operations are described as producing a pointer to a
suitable created object. These operations select one of the
implicitly-created objects whose address is the address of the start
of the region of storage, and produce a pointer value that points to
that object, if that value would result in the program having defined
behavior. If no such pointer value would give the program defined
behavior, the behavior of the program is undefined. If multiple such
pointer values would give the program defined behavior, it is
unspecified which such pointer value is produced.
The example given in C++20 spec is:
#include <cstdlib>
struct X { int a, b; };
X *make_x() {
// The call to std​::​malloc implicitly creates an object of type X
// and its subobjects a and b, and returns a pointer to that X object
// (or an object that is pointer-interconvertible ([basic.compound]) with it),
// in order to give the subsequent class member access operations
// defined behavior.
X *p = (X*)std::malloc(sizeof(struct X));
p->a = 1;
p->b = 2;
return p;
}
As for the use of memcpy - #Shafik Yaghmour already addresses that, this part is valid for trivially copyable types (the wording changed from POD in C++98 and C++03 to trivially copyable types in C++11 and after).
Bottom line: the code is valid.
As for the question of lifetime, let's dig into the code in question:
struct T // trivially copyable type
{
int x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) ); // <= just an allocation
if ( !buf ) return 0;
T a{}; // <= here an object is born of course
std::memcpy(buf, &a, sizeof a); // <= just a copy of bytes
T *b = static_cast<T *>(buf); // <= here an object is "born"
// without constructor
b->x = b->y;
free(buf);
}
Note that one may add a call to the destructor of *b, for the sake of completeness, before freeing buf:
b->~T();
free(buf);
though this is not required by the spec.
Alternatively, deleting b is also an option:
delete b;
// instead of:
// free(buf);
But as said, the code is valid as is.

From a quick search.
"... lifetime begins when the properly-aligned storage for the object is allocated and ends when the storage is deallocated or reused by another object."
So, I would say by this definition, the lifetime begins with the allocation and ends with the free.

Is this code correct?
Well, it will usually "work", but only for trivial types.
I know you did not ask for it, but lets use an example with a non-trivial type:
#include <cstdlib>
#include <cstring>
#include <string>
struct T // trivially copyable type
{
std::string x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T a{};
a.x = "test";
std::memcpy(buf, &a, sizeof a);
T *b = static_cast<T *>(buf);
b->x = b->y;
free(buf);
}
After constructing a, a.x is assigned a value. Let's assume that std::string is not optimized to use a local buffer for small string values, just a data pointer to an external memory block. The memcpy() copies the internal data of a as-is into buf. Now a.x and b->x refer to the same memory address for the string data. When b->x is assigned a new value, that memory block is freed, but a.x still refers to it. When a then goes out of scope at the end of main(), it tries to free the same memory block again. Undefined behavior occurs.
If you want to be "correct", the right way to construct an object into an existing memory block is to use the placement-new operator instead, eg:
#include <cstdlib>
#include <cstring>
struct T // does not have to be trivially copyable
{
// any members
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T *b = new(buf) T; // <- placement-new
// calls the T() constructor, which in turn calls
// all member constructors...
// b is a valid self-contained object,
// use as needed...
b->~T(); // <-- no placement-delete, must call the destructor explicitly
free(buf);
}

Related

When I perform placement new on trivial object, Is it guaranteed to preserve the object/value representation?

struct A
{
int x;
}
A t{};
t.x = 5;
new (&t) A;
// is it always safe to assume that t.x is 5?
assert(t.x == 5);
As far as I know, when a trivial object of class type is created, the compiler can omit the call of explicit or implicit default constructor because no initialization is required.
(is that right?)
Then, If placement new is performed on a trivial object whose lifetime has already begun, is it guaranteed to preserve its object/value representation?
(If so, I want to know where I can find the specification..)
Well, let's ask some compilers for their opinion. Reading an indeterminate value is UB, which means that if it occurs inside a constant expression, it must be diagnosed. We can't directly use placement new in a constant expression, but we can use std::construct_at (which has a typed interface). I also modified the class A slightly so that value-initialization does the same thing as default-initialization:
#include <memory>
struct A
{
int x;
constexpr A() {}
};
constexpr int foo() {
A t;
t.x = 5;
std::construct_at(&t);
return t.x;
}
static_assert(foo() == 5);
As you can see on Godbolt, Clang, ICC, and MSVC all reject the code, saying that foo() is not a constant expression. Clang and MSVC additionally indicate that they have a problem with the read of t.x, which they consider to be a read of an uninitialized value.
P0593, while not directly related to this issue, contains an explanation that seems relevant:
The properties ascribed to objects and references throughout this document apply for a given object or reference only during its lifetime.
That is, reusing the storage occupied by an object in order to create a new object always destroys whatever value was held by the old object, because an object's value dies with its lifetime. Now, objects of type A are transparently replaceable by other objects of type A, so it is permitted to continue to use the name t even after its storage has been reused. That does not imply that the new t holds the value that the old t does. It only means that t is not a dangling reference to the old object.
Going off what is said in P0593, GCC is wrong and the other compilers are right. In constant expression evaluation, this kind of code is required to be diagnosed. Otherwise, it's just UB.
From looking at the Standard, the program has undefined behavior because of an invalid use of an object with indeterminate value.
Per [basic.life]/8, since the object of type A created by the placement new-expression exactly overlays the original object t, using the name t after that point refers to the A object created by the new-expression.
In [basic.indet]/1, we have:
When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]).
One important detail here (which I missed at first) is that "obtaining storage" is different from "allocating storage" or the storage duration of a storage region. The "obtain storage" words are also used to define the beginning of an object's lifetime in [basic.life]/1 and in the context of a new-expression in [expr.new]/10:
A new-expression may obtain storage for the object by calling an allocation function ([basic.stc.dynamic.allocation]). ... [ Note: ... The set of allocation and deallocation functions that may be called by a new-expression may include functions that do not perform allocation or deallocation; for example, see [new.delete.placement]. — end note ]
So the placement new-expression "obtains storage" for the object of type A and its subobject of type int when it calls operator new(void*). For this purpose, it doesn't make a difference that the memory locations in the storage region actually have static storage duration. Since "no initialization is performed" for the created subobject of type int with dynamic storage duration, it has an indeterminate value.
See also this Q&A: What does it mean to obtain storage?

Does std::construct_at make an array member of a union active?

Look at this example (godbolt):
#include <memory>
union U {
int i[1];
};
constexpr int foo() {
U u;
std::construct_at(u.i, 1);
return u.i[0];
}
constexpr int f = foo();
gcc and msvc successfully compile this, but clang complains:
construction of subobject of member 'i' of union with no active member is not allowed in a constant expression
Which compiler is right? I think that clang is wrong here, because C++20's implicit creation of objects (P0593) should make this program valid (because the array should be implicitly created, which should make u.i active), but I'm not sure.
U u;
does not begin the lifetime of the i subobject. Beginning the lifetime of a variable other than an array of type char, unsigned char or std::byte is also not one of the operations specifically qualified to be implicitly creating objects. [basic.intro.object]/13
Therefore at this point the i member is definitively not active and the array object is not alive.
As mentioned by #Sebastian in the question comments, calling std::construct_at on u.i is then not allowed in a constant expression since [expr.const]/6.1 specifically requires the provided pointer to point to an object whose lifetime began during the evaluation of the constant expression (or be storage returned from std::allocator).
Therefore Clang seems correct to me. There is an open GCC bug for exactly this issue here.
I am not sure that this is the intended interpretation though, since Clang does accept the program if a non-array type is used for the member, which by my reasoning would equally not be allowed.
The relevant wording is a consequence of this comment.
In any case, it is not intended that implicit object creation happens in constant expressions although it currently seems to (question), see CWG issue 2469.
Without implicit object creation as explained below, the use in a context requiring a constant expression should then be ill-formed independently of the std::construct_at restriction and the following considerations.
Whether the construction has defined behavior if used outside a constant expression context, I am not entirely sure.
But I think that std::construct_at being specified to be equivalent to a new-expression means that it will call operator new, which is specified to implicitly create objects in the storage it returns. [basic.intro.object]/13
Whether operator new must be an allocating operator new call for this to be true is not fully clear to me. I think the wording "in the returned region of storage" does not require it.
i is of type int[1], which is an implicit-lifetime type, which are implicitly created if necessary by operations qualified to implicitly create objects. [basic.types.general]/9
Therefore I think that construct_at will implicitly create the an array object at u.i and begin its lifetime. I also think that [basic.intro.object]/2 will guarantee that this object becomes subobject of the union, so that u.i will refer to it.
However, given that the storage operated on is only the size of a single int and assuming that this is also the storage meant in [basic.intro.object]/13, only an array of length 1 can be implicitly created in it. Therefore if i was of length larger than 1, the implicitly created array could not overlap exactly with the member and can therefore not become subobject of the union.
In this case implicit object creation could not make return u.i[0]; defined behavior.
There is a discussion of this issue here which seems to indicate that already forming the pointer to the first element of u.i outside its lifetime is UB, in which case the construct_at version with array would more directly have UB, but at least compilers accept both auto x = u.i; and auto x = &u.i[0]; in a constant expression without complaining. As mentioned in the comments to this answer, this also seems wrong.
All in all I think that std::construct_at can generally not be used to activate an array member of a union.
But, suppose you replace the std::construct_at call with
u.i[0] = 1;
Then this assignment will begin the lifetime of the array object, as described in [class.union.general]/6. This is not disqualified for constant expressions since C++20 either. Therefore the code will not be ill-formed if used in a context requiring a constant expression, nor will it have undefined behavior outside of that.
Deferred initialization of an array in a constexpr environment can be achieved with std::allocator and std::construct_at():
#include <memory>
constexpr int foo() {
std::allocator<int> alloc;
int* i; // pointer to first element of array
i = alloc.allocate(100); // allocate memory for 100 elements
std::construct_at(&i[0], 1); // initialize first element (first call of constructor)
int r = i[0];
alloc.deallocate(i, 100); // deallocate before leaving
return r;
}
constexpr int f = foo();
Pointers to the relevant standard clauses:
allocate:
[utilities.memory.default.allocator.members]/5: std::allocator<>::allocate() obtains storage by calling operator ::new and starts the lifetime of the array object, but not the lifetime of the array elements themselves.
[expr.const]/5.19: Explicitly allows std::allocator<>::allocate() in constant expressions, if the memory is deallocated again within the constant expression
construct_at:
[algorithms.specialized.construct]/2: std::construct_at() effectively calls placement new.
[expr.const]/6: Explicitly allows std::construct_at in constant expressions, if the memory is allocated by std::allocator
deallocate:
[expr.const]/5.19: Explicitly allows std::allocator<>::deallocate() in constant expressions, if the memory was allocated before within the constant expression

About aligned storage and trivially copyable/destructible types

I had an interesting discussion with a guy smarter than me and I remained with an open question about aligned storage and trivially copyable/destructible types.
Consider the following example:
#include <type_traits>
#include <vector>
#include <cassert>
struct type {
using storage_type = std::aligned_storage_t<sizeof(void *), alignof(void *)>;
using fn_type = int(storage_type &);
template<typename T>
static int proto(storage_type &storage) {
static_assert(std::is_trivially_copyable_v<T>);
static_assert(std::is_trivially_destructible_v<T>);
return *reinterpret_cast<T *>(&storage);
}
std::aligned_storage_t<sizeof(void *), alignof(void *)> storage;
fn_type *fn;
bool weak;
};
int main() {
static_assert(std::is_trivially_copyable_v<type>);
static_assert(std::is_trivially_destructible_v<type>);
std::vector<type> vec;
type t1;
new (&t1.storage) char{'c'};
t1.fn = &type::proto<char>;
t1.weak = true;
vec.push_back(t1);
type t2;
new (&t2.storage) int{42};
t2.fn = &type::proto<int>;
t2.weak = false;
vec.push_back(t2);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto &t) { return t.weak; }), vec.end());
assert(vec.size() == 1);
assert(!vec[0].weak);
assert(vec[0].fn(vec[0].storage) == 42);
}
This is a simplified version of a real world case. I really hope I didn't make errors or simplified it too much.
As you can see, the idea is that there exists a type called type (naming things is hard, you know) having three data members:
storage that is a bunch of byte having size sizeof(void *)
fn a pointer to a function having type int(storage_type &)
weak an useless bool used only to introduce the example
To create new instances of type (see the main function), I put a value (either an int or a char) in the storage area and the right specialization of the static function template proto in fn.
Later on, when I want to invoke fn and get the integer value it returns, I do something like this:
int value = type_instance.fn(type_instance.storage);
So far, so good. Despite the fact of being risky and error-prone (but this is an example, the real use case is not), this works.
Note that type and all the types I put in the storage (int and char in the example) are required to be both trivially copyable and trivially destructible. This is also the core of the discussion I had.
The problem (or better, the doubt) arises when I put instances of types eg in a vector (see the main function) and decide to remove one of them from within the array, so that some of the others are moved around to keep it packed.
More in general, I'm no longer that sure about what happens when I want to copy or move instances of type and if it's UB or not.
My guess was that it was allowed being the types put in the storage trivially copyable and trivially destructible. On the other side, I've been told that this isn't directly allowed by the standard and it can be considered a benign UB, because almost all the compilers in fact allows you to do that (I can guarantee this, it seemes to work everywhere for some definitions of work).
So, the question is: is this allowed or UB and what can I do to work around the issue in the second case? Moreover, is C++20 going to change things for that?
This problem reduces to basically what LanguageLawyer suggested:
alignas(int) unsigned char buff1[sizeof(int)];
alignas(int) unsigned char buff2[sizeof(int)];
new (buff1) int {42};
std::memcpy(buff2, buff1, sizeof(buff1));
assert(*std::launder(reinterpret_cast<int*>(buff2)) == 42); // is it ok?
In other words - when I copy bytes around, do I also copy around "object-ness"? buff1 is certainly providing storage for an int - when we copy those bytes, does buff2 also now provide storage for an int?
And the answer is... no. There are exactly four ways to create an object, per [intro.object]:
An object is created by a definition, by a new-expression ([expr.new]), when implicitly changing the active member of a union, or when a temporary object is created ([conv.rval], [class.temporary]).
None of those things happened here, so we don't have an object in buff2 of any kind (outside of just the normal array of unsigned char), hence behavior is undefined. Simply put, memcpy does not create objects.
In the original example, it's only the 3rd line that requires that implicit object creation:
assert(vec.size() == 1); // ok
assert(!vec[0].weak); // ok
assert(vec[0].fn(vec[0].storage) == 42); // UB
This is why P0593 exists and has a special section for memmove/memcpy:
A call to memmove behaves as if it
copies the source storage to a temporary area
implicitly creates objects in the destination storage, and then
copies the temporary storage to the destination storage.
This permits memmove to preserve the types of trivially-copyable objects, or to be used to reinterpret a byte representation of one object as that of another object.
This is what you need here - that implicit object creation step is currently missing from C++ today.
That said, you can more or less rely on this "doing the right thing" given the simply enormous body of C++ code that exists today relies on this code to "just work."

Copy trivially copyable types using temporary storage areas: is it allowed?

This question is a follow up of a comment to an answer of another question.
Consider the following example:
#include <cstring>
#include <type_traits>
#include <cassert>
int main() {
std::aligned_storage_t<sizeof(void*), alignof(void*)> storage, copy;
int i = 42;
std::memcpy(&storage, &i, sizeof(int));
copy = storage;
int j{};
std::memcpy(&j, &copy, sizeof(int));
assert(j == 42);
}
This works (for some definitions of works). However, the standard tells us this:
For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes making up the object can be copied into an array of char, unsigned char, or std​::​byte .
If the content of that array is copied back into the object, the object shall subsequently hold its original value. [ Example:
#define N sizeof(T)
char buf[N];
T obj; // obj initialized to its original value
std::memcpy(buf, &obj, N); // between these two calls to std​::​memcpy, obj might be modified
std::memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type holds its original value
 — end example ]
And this:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1. [ Example:
T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p
 — end example ]
In any case it mentions that copying a trivially copyable type in a buffer and then copy it back in a new instance of the original type is allowed.
In the example above I do something similar, plus I copy also the buffer in a new buffer (this resembles a bit more the real world case).
In the comments linked at the top of the question, the author says that this behavior is underspecified. On the other side, I cannot see eg how could I send an int over the network and use it on the other end if this isn't allowed (copy an int in a buffer, send it over the network, receive it as a buffer and memcpy it in an instance of int - more or less what I do in the example, without a network in between).
Is this allowed by some other bullets of the standard I missed or is this really underspecified?
It reads fine to me.
You've copied the underlying bytes of obj1 into obj2. Both are trivial and of the same type. The prose you quote permits this explicitly.
The fact that said underlying bytes were temporarily stored in a correctly-sized and correctly-aligned holding area, via an also-explicitly-permitted reinterpretation as char*, doesn't seem to change that. They're still "those bytes". There's no rule that says copying must be "direct" in order to satisfy features like this.
Indeed, this is not only a completely common pattern when dealing with network transfer (conventional use of course doesn't make it right on its own), but also a historically normal thing to do that the standard would be mad not to account for (which gives me all the assurance I need that it is indeed intended).
I can see how there may be doubt, given that the rule is first given for copying those bytes back into the original object, then given again for copying those bytes into a new object. But I can't detect any logical difference between the two circumstances, and therefore find the first quoted wording to be largely redundant. It's possible the author just wanted to be crystal clear that this safety applies identically in both cases.
To me, this is one of the most ambiguous issues in C++. Honestly speaking, I never got confused by anything in C++ as much as type punning. There's always a corner case that seems to be not covered (or underspecified, like you put it).
However, conversion from integers to raw memory (char*) is supposed to be allowed for serialization/examination of underlying object.
What's the solution?
Unit tests. That's my solution to the problem. You do what complies most with the standard, and you write basic unit tests that test your particular assumption. Then, whenever you compile a new version or move to a new compiler, you run the unit tests and verify that the compiler does what you expect it to do.

memmove in-place change of effective type (type-punning)

In the following question:
What's a proper way of type-punning a float to an int and vice-versa?, the conclusion is that the way to construct doubles from integer bits and vise versa is via memcpy.
That's fine, and the pseudo_cast conversion method found there is:
template <typename T, typename U>
inline T pseudo_cast(const U &x)
{
static_assert(sizeof(T) == sizeof(U));
T to;
std::memcpy(&to, &x, sizeof(T));
return to;
}
and I would use it like this:
int main(){
static_assert(std::numeric_limits<double>::is_iec559);
static_assert(sizeof(double)==sizeof(std::uint64_t));
std::uint64_t someMem = 4614253070214989087ULL;
std::cout << pseudo_cast<double>(someMem) << std::endl; // 3.14
}
My interpretation from just reading the standard and cppreference is/was that is should also be possible to use memmove to change the effective type in-place, like this:
template <typename T, typename U>
inline T& pseudo_cast_inplace(U& x)
{
static_assert(sizeof(T) == sizeof(U));
T* toP = reinterpret_cast<T*>(&x);
std::memmove(toP, &x, sizeof(T));
return *toP;
}
template <typename T, typename U>
inline T pseudo_cast2(U& x)
{
return pseudo_cast_inplace<T>(x); // return by value
}
The reinterpret cast in itself is legal for any pointer (as long as cv is not violated, item 5 at cppreference/reinterpret_cast). Dereferencing however requires memcpy or memmove (§6.9.2), and T and U must be trivially copyable.
Is this legal? It compiles and does the right thing with gcc and clang.
memmove source and destinations are explicitly allowed to overlap, according
to cppreference std::memmove and memmove,
The objects may overlap: copying takes place as if the characters were
copied to a temporary character array and then the characters were
copied from the array to dest.
Edit: originally the question had a trivial error (causing segfault) spotted by #hvd. Thank you! The question remains the same, is this legal?
C++ does not allow a double to be constructed merely by copying the bytes. An object first needs to be constructed (which may leave its value uninitialised), and only after that can you fill in its bytes to produce a value. This was underspecified up to C++14, but the current draft of C++17 includes in [intro.object]:
An object is created by a definition (6.1), by a new-expression (8.3.4), when implicitly changing the active member of a union (12.3), or when a temporary object is created (7.4, 15.2).
Although constructing a double with default initialision does not perform any initialisation, the construction does still need to happen. Your first version includes this construction by declaring the local variable T to;. Your second version does not.
You could modify your second version to use placement new to construct a T in the same location that previously held an U object, but in that case, when you pass &x to memmove, it is no longer required to read the bytes that had made up x's value, because the object x has already been destroyed by the earlier placement new.
My reading of the standard suggests that both these functions will result in UB.
consider:
int main()
{
long x = 10;
something_with_x(x*10);
double& y = pseudo_cast_inplace<double>(x);
y = 20;
something_with_y(y*10);
}
Because of the strict alias rule, it seems to me that there's nothing to stop the compiler from reordering instructions to produce code as-if:
int main()
{
long x = 10;
double& y = pseudo_cast_inplace<double>(x);
y = 20;
something_with_x(x*10); // uh-oh!
something_with_y(y*10);
}
I think the only legal way to write this is:
template <typename T, typename U>
inline T pseudo_cast(U&& x)
{
static_assert(sizeof(T) == sizeof(U));
T result;
std::memcpy(std::addressof(result), std::addressof(x), sizeof(T));
return result;
}
Which in reality results in the exact same assembler output (i.e. none whatsoever - the entire function is elided, as are the variables themselves) - at least on gcc with -O2
This should be legal in C++20. Example in godbolt.
template <typename T, typename U>
requires (
sizeof(U) >= sizeof(T) and
std::alignment_of_v<T> <= std::alignment_of_v<U> and
std::is_trivially_copyable_v<T> and
std::is_trivially_destructible_v<U>
)
[[nodiscard]] T& reinterpret_object(U& obj)
{
// Get access to object representation
std::byte* bytes = reinterpret_cast<std::byte*>(&obj);
// Copy object representation to temporary buffer.
// Implicitly create a T object in the destination storage. The lifetime of U object ends.
// Copy temporary buffer back.
void* storage = std::memmove(bytes, bytes, sizeof(T));
// Storage pointer value is 'pointer to T object', so we are allowed to cast it to the proper pointer type.
return *static_cast<T*>(storage);
}
reinterpret_cast to a different pointer type is allowed (7.6.1.10)
An object pointer can be explicitly converted to an object pointer of a different type.
Accessing the object representation through an std::byte* pointer is allowed (7.2.1)
If a program attempts to access the stored value of an object through a glvalue whose type is not similar to one of the following types the behavior is undefined
a char, unsigned char, or std​::​byte type.
std::memmove behaves as-if copying to a temporary buffer and can implicitly create objects (21.5.3)
The functions memcpy and memmove are signal-safe.
Both functions implicitly create objects ([intro.object]) in the destination region of storage immediately prior to copying the sequence of characters to the destination.
Implicit object creation is described in (6.7.2)
Some operations are described as implicitly creating objects within a specified region of storage.
For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types ([basic.types]) in its specified region of storage if doing so would result in the program having defined behavior.
If no such set of objects would give the program defined behavior, the behavior of the program is undefined.
If multiple such sets of objects would give the program defined behavior, it is unspecified which such set of objects is created.
[Note 4: Such operations do not start the lifetimes of subobjects of such objects that are not themselves of implicit-lifetime types.
— end note]
Further, after implicitly creating objects within a specified region of storage, some operations are described as producing a pointer to a suitable created object.
These operations select one of the implicitly-created objects whose address is the address of the start of the region of storage, and produce a pointer value that points to that object, if that value would result in the program having defined behavior.
If no such pointer value would give the program defined behavior, the behavior of the program is undefined.
If multiple such pointer values would give the program defined behavior, it is unspecified which such pointer value is produced.
It is not specified that std::memmove is such a function and its returned pointer value would be a pointer to the implicitly created object.
But it makes sense that is is so.
Returning a pointer to the new object is allowed by (7.6.1.9)
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.
If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified.
Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b.
Otherwise, the pointer value is unchanged by the conversion.
If std::memmove does not return a usable pointer value, std::launder<T>(reinterpret_cast<T*>(bytes)) (17.6.5) should be able to produce such a pointer value.
Additional notes:
I'm not 100% sure if all the requires are correct or some condition is missing.
To get zero overhead, the compiler must to optimize the std::memmove away (gcc and clang seem to do it).
The lifetime of the original object ends (6.7.3)
A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling a destructor or pseudo-destructor ([expr.prim.id.dtor]) for the object.
This means that using the original name or pointers or references to it will result in undefined behaviour.
The object can be "revived" by reinterpreting it back reinterpret_object<U>(reinterpret_object<T>(obj)) and that should allow using the old references (6.7.3)
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 ([intro.object]), 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.
The object representations should be "compatible", interpreting the bytes of the original object as bytes of the new one can produce "garbage" or even trap representations.
Accessing a double while the actual type is uint64_t is undefined behavior because compiler will never consider that an object of type double can share the address of an object of type uint64_t intro.object:
Unless an object is a bit-field or a base class subobject of zero size, the address of that object is the address of the first byte it occupies.
Two objects a and b with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a base class subobject of zero size and they are of different types; otherwise, they have distinct addresses.