Do the following class break the strict aliasing rule:
template<typename T>
class store {
char m_data[sizeof(T)];
bool m_init;
public:
store() : m_init(false) {}
store(const T &t) : init(true) {
new(m_data) T(t);
}
~store() {
if(m_init) {
get()->~T();
}
}
store &operator=(const store &s) {
if(m_init) {
get()->~T();
}
if(s.m_init) {
new(m_data) T(*s.get());
}
m_init = s.m_init;
}
T *get() {
if (m_init) {
return reinterpret_cast<T *>(m_data);
} else {
return NULL;
}
}
}
My reading of a standard is that it is incorrect but I am not sure (my usage is to have an array of objects T + some metadata of those objects but to have control over the object construction/deconstruction without manually allocating memory) as the allocated objects are used as examples for placement new in standard.
The standard contains this note:
[ Note: A typical implementation would define aligned_storage as:
template <std::size_t Len, std::size_t Alignment>
struct aligned_storage {
typedef struct {
alignas(Alignment) unsigned char __data[Len];
} type;
};
— end note ]
— Pointer modifications [meta.trans.ptr] 20.9.7.5/1
And aligned_storage is defined in part with:
The member typedef type shall be a POD type suitable for use as uninitialized storage for any object whose size is at most Len and whose alignment is a divisor of Align.
The only property covered by the standard that restricts the addresses at which an object can be constructed is alignment. An implementation might have some other restrictions, however I'm not familiar with any that do. So just ensure that having correct alignment is enough on your implementation and I think this should be okay. (and in pre-C++11 compilers you can use use compiler extensions for setting alignment such as __attribute__((alignment(X))) or __declspec(align(X)).
I believe that as long as you don't access the underlying storage directly the aliasing rules don't even come into the picture, because the aliasing rules cover when it is okay to access the value of an object through an object of a different type. Constructing an object and accessing only that object doesn't involve accessing the object's value through an object of any other type.
Earlier answer
The aliasing rules specifically allow char arrays to alias other objects.
If a program attempts to access the stored value of an object through
a glvalue of other than one of the following types the behavior is
undefined:
[...]
— a char or unsigned char type.
— Lvalues and rvalues [basic.lval] 3.10/10
You do need to make sure that the array is properly aligned for type T though.
alignas(T) char m_data[sizeof(T)];
The above is C++11 syntax for setting alignment, but if you're on a C++03 compiler then you'll need a compiler specific attribute to do the same thing. GCC has __attribute__((aligned(32))) and MSVC has __declspec(align(32))
Kerrek SB brings up a good point that the aliasing rules state that it's okay to access the value of a T object via a char array, but that may not mean that accessing the value of a char array via a T object is okay. However, if the placement new expression is well defined then that creates a T object which I think it's okay to accesses as a T object by definition, and reading the original char array is accessing the value of the created T object, which is covered under the aliasing rules.
I think that implies that you could store a T object in, for example, an int array, and as long as you don't access the value of that T object through the original int array then you're not hitting undefined behavior.
What is allowed is to take a T object and interpret it as an array of chars. However, it is in general not allowed to take an arbitrary array of chars and treat it as a T, or even as the pointer to an area of memory containing a T. At the very least, your char array would need to be properly aligned.
One way around this might be to use a union:
union storage { char buf[sizeof(T)]; T dummy; };
Now you can construct a T inside storage.buf:
T * p = ::new (storage.buf) T();
Related
first a word in advance: The following code should not be used as it is and is just the condense of working code to the critical point. The question is only where does the following code violate the standard (C++17, but C++20 is also fine) and if it doesn't whether the standard guarantees the "correct output"? It is not an example for beginners how to write code or anything like that, it is purely a question about the standard. (On request via pm: alternative version further below)
Assume for the following that the class Base is never directly instantiated, but only via Derived<Size> for some std::size_t Size. Otherwise the undefined behaviour is obvious.
#include <cstddef>
struct Header
{ const std::size_t m_size; /* more stuff, remains standard layout */ };
struct alignas(Header) Base
{
std::size_t getCapacity()
{ return getHeader().m_size; }
std::byte *getBufferBegin() {
// Allowed by [basic.lval] (11.8)
return reinterpret_cast<std::byte *>(this);
// Does this give the same as the following code (which has to be commented out as Size is unknown):
// // Assume this "is actually an instance of Derived<Size>" for some Size, then
// // [expr.static.cast]-11 allows
// Derived<Size> * me_p = static_cast<Derived<Size> *>(this);
// // [basic.compound].4 + 4.3: say that
// // instances of standard-layout types and its first member are pointer-interconvertible:
// Derived<Size>::memory_type * data_p = reinterpret_cast<memory_type *>(me_p);
// Derived<Size>::memory_type & data = *data_p;
// // Degregation from array to pointer is allowed
// std::byte * begin_p = static_cast<std::byte *>(data);
// return begin_p;
}
std::byte * getDataMemory(int idx)
{
// For 0 <= idx < "Size", this is guaranteed to be valid pointer arithmetic
return getBufferBegin() + sizeof(Header) + idx * sizeof(int);
}
Header & getHeader()
{
// This is one of the two purposes of launder (see Derived::Derived for the in-place new)
return *std::launder(reinterpret_cast<Header *>(getBufferBegin()));
}
int & getData(int idx)
{
// This is one of the two purposes of launder (see Derived::Derived for the in-place new)
return *std::launder(reinterpret_cast<int*>(getDataMemory(idx)));
}
};
template<std::size_t Size>
struct Derived : Base
{
Derived() {
new (Base::getBufferBegin()) Header { Size };
for(int idx = 0; idx < Size; ++idx)
new (Base::getDataMemory(idx)) int;
}
~Derived() {
// As Header (and int) are trivial types, no need to call the destructors here
// as there lifetime ends with the lifetime of their memory, but we could call them here
}
using memory_type = std::byte[sizeof(Header) + Size * sizeof(int)];
memory_type data;
};
The question is not whether the code is nice, not whether you should do this, and not whether it will work in every single or any specific compiler - and please also forget alignment/padding for absurd compilers ;). Thus, please do not comment on style, whether one should do this, on missing const etc or what to take care of when generalizing that (padding, alignment etc), but only
where it violates the standard and if it doesn't
is it guaranteed to work (ie. getBufferBegin returns the begin of the buffer)
Please be so kind to refer to the standard for any answer!
Thanks a lot
Chris
Alternative
Edited: Both equivalent, answer what ever you like more...
As there seems quite a lot of misunderstanding and nobody reading explaining comments :-/, let me "rephrase" the code in an alternative version containing the same questions. In three steps:
Call getDataN<100>(static_cast<void*>(&d)); and getData4(static_cast<Base*>(&d)); for an instance Derived<100> d
struct Data { /* ... remains standard layout, not empty */ };
struct alignas(Data) Base {};
template<std::size_t Size>
struct Derived { Data d; };
// Definitiv valid
template<std::size_t Size>
Data * getData1a(void * ptr)
{ return static_cast<Derived<Size>*>(ptr)->d; }
template<std::size_t Size>
Data * getData1b(Base * ptr)
{ return static_cast<Derived<Size>*>(ptr)->d; }
// Also valid: First element in standard layout
template<std::size_t Size>
Data * getData2(void * ptr)
{ return reinterpret_cast<Data *>(static_cast<Derived<Size>*>(ptr)); }
// Valid?
Data * getData3(void * ptr)
{ return reinterpret_cast<Data *>(ptr); }
// Valid?
Data * getData4(Base* ptr)
{ return reinterpret_cast<Data *>(ptr); }
call getMemN<100>(static_cast<void*>(&d));/getMem5(static_cast<Data*>(&d)); for an Data<100> d
template<std::size_t Size>
using Memory = std::byte data[Size];
template<std::size_t Size>
struct Data { Memory data; };
template<std::size_t Size>
std::byte *getMem1(void * ptr)
{ return &(static_cast<Data[Size]*>(ptr)->data[0]); }
// Also valid: First element in standard layout
template<std::size_t Size>
std::byte *getMem2(void * ptr)
{ return std::begin(*reinterpret_cast<Memory *>(static_cast<Data[Size]*>(ptr))); }
template<std::size_t Size>
std::byte *getMem3(void * ptr)
{ return static_cast<std::byte*>(*reinterpret_cast<Memory *>(static_cast<Data[Size]*>(ptr))); }
template<std::size_t Size>
std::byte *getMem4(void * ptr)
{ return *reinterpret_cast<std::byte**>(ptr); }
std::byte *getMem4(Data * ptr)
{ return *reinterpret_cast<std::byte**>(ptr); }
the trivial
std::byte data[100];
new (std::begin(data)) std::int32_t{1};
new (std::begin(data) + 4) std::int32_t{2};
// ...
std::launder(reinterpret_cast<std::int32_t*>(std::begin(data))) = 3;
std::launder(reinterpret_cast<std::int32_t*>(std::begin(data) + 4)) = 4;
std::launder(reinterpret_cast<std::int32_t*>(std::begin(data))) = 5;
std::launder(reinterpret_cast<std::int32_t*>(std::begin(data) + 4)) = 6;
Still unclear
The argumentation below that a base class of a standard layout class is pointer-interconvertible to a derived class is incorrect. More precisely, it holds only if the derived class wouldn't have any member (including member of a base class). Therefore, the strange discussion using C is not working as C inherits the members of Derived and calls them members of C.
As Base and Derived are not pointer interconvertible, the usage of std::launder to access to the data of Derived (see below) is against the standard as the object representation of Derived is not accessible from the pointer to the Base instance. So even if a pointer to Base has the same value as a pointer to Derived, the access via Base::getHeader would not necessarily be defined behaviour - probably undefined behaviour as there is no reason to think otherwise.
Note: The compiler cannot assume that this data is not accessed via a Base pointer, as the data is accessible after an static_cast to Derived and therefore no optimization may be applied to this data. However, it remains that it is undefined behaviour if you used an reinterpret_cast (even if the value of the pointer is the same).
Question: Is there anything in the standard enforcing that a pointer to Derived is also a pointer to Base? They explicitly might have the same address, but are they guaranteed to? (at least for standard layout...).
Or put differently, is reinterpret_cast<Base*>(&d) for a Derived d
a well-defined pointer to the base subobject? (Regardless of accessibility)
PS: With C++20, we have std::is_pointer_interconvertible_base_of with which we can check, whether it holds for the given types.
Old answer
Yes, the presented code is both well-defined and behaves as expected. Let us look at the critical methods Base::getBufferBegin, Base::getData, and Base::getHeader one by one.
Base::getBufferBegin
First let us show a sequence of well-defined casts which will make the requested cast from the this pointer to a pointer to the first element in the array data in the Derived instance. And then secondly, show that the given reinterpret_cast is well-defined and gives the right result. For simplification, forget about member functions as a first step.
using memory_type = std::byte[100];
Derived<100> & derived = /* what ever */;
Base * b_p {&derived}; // Definition of this, when calling a member function of base.
// 1) Cast to pointer to child: [expr.static.cast]-11
// "A prvalue of type “pointer to B”, where B is a class type, can be converted to a prvalue
// of type “pointer to D”, where D is a class derived(Clause 13) from B."
// allowing for B=Base, D=Derived<100>
auto * d_p = static_cast<Derived<100> *>(b_p);
// 3. Cast to first member (memory_type ) is valid and does not change the value
// [basic.compound].4 + 4.3: -> standard-layout and first member are
// pointer-interconvertible, so the following is valid:
memory_type * data_p = reinterpret_cast<memory_type *>(d_p);
// 4. Cast to pointer to first element is valid and does not change the value
// [dcl.array].1 "An object of array type contains a contiguously allocated non-empty set of
// N subobjects of type T."
// [intro.object].8 "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."
// [expr.sizeof]. "When applied to an array, the result is the total number of bytes in the
// array. This implies that the size of an array of n elements is n times the size of an
// element." Thus, casting to the binary representation (by [basic.lval].11 always allowed!)
std::byte * begin_p = reinterpret_cast<std::byte *>(data_p); // Note: pointer to array!
// results in the same as std::byte * begin_p = std::begin(*data_p)
A reinterpret_cast does not change the value of the given pointer², so if the first cast can be replaced by an reinterpret_cast without changing the resulting value, then the result of the above gives the same value as std::byte * begin_p = reinterpret_cast<std::byte *>(b_p);
[basic.compound].4 + 4.3 says (rephrased) Pointer to a instance of a standard-layout class without members is pointer-interconvertible has same address as any of its base classes. Thus, if C would be a standard layout child class of Derived<100>, then a pointer to an instance of C would be pointer-interconvertible to the a pointer to the sub-object Derived<100> and to one to the sub-object Base. By transitivity of pointer-interconvertibility ([basic.compound].4.4) a pointer to Base is pointer-interconvertible to a pointer to Derived<100> if such a class existed. Either we define C<Size> to be such a class and use C<100> instead of Derived<Size> or be just accept that it is not predictable from any object file whether there could be such a class C, so the only way to ensure this is that these two are pointer-interconvertible regardless of such a class C (and its existence). In particular, auto * d_p = reinterpret_cast<Derived<100> *>(b_p); can be used instead of the static_cast.
Missing: auto * d_p = reinterpret_cast<Derived<100> *>(b_p); can be used instead of the static_cast.
Last step for Base;;getBuferBegin, can we replace all the above by *reinterpret_cast<std::byte*>(b_p);. First of all, yes we are allowed to do this cast as casting to the binary representation (by [basic.lval].11) is always allowed and does not change the value of the pointer²! Secondly, this cast gives the same result as we just have shown that the casts above can all be replaced by reinterpret_casts (not changing the value²).
All in all this shows that Base::getBufferBegin() is well-defined and behaves as expected (the returned pointer points to the first element in the buffer data of the child class).
Base::getHeader
The constructor of Derived<Size> constructs a header instance at the first byte of the array data. By the above Base::getBufferBegin gives a pointer to exactly this byte. The question remains, whether we are allowed to access the header through this pointer. For simplicity, let me cite cppreference here (ensuring that the same is in the standard (but less understandable)): Citing the "notes" from there
Typical uses of std::launder include: [...] Obtaining a pointer to an object created by placement new from a pointer to an object providing storage for that object.
Which is exactly what we are doing here, so everything is fine, isn't it? No, not yet. Looking at the requirements of std::launder, we need that "every byte that would be reachable through the result is reachable through p [the given pointer]". But is this the case here? The answer is yes, it surprisingly is. By the above argumentation (just search for [basic.compound].4.4 ;)) gives that a pointer to Base is pointer-interconvertible to Derived<Size>. Per definition of reachability of a byte via an pointer, this means that the full binary representation of Derived<Size> is reachable by a pointer to Base (note that this is only true for standard layout classes!). Thus, reinterpret_cast<Header*>(this); gives a pointer to the Header-instance through which every byte of the binary representation of Header is reachable, satisfying the conditions of std::launder. Thus, std::launder (being a noop) results in a valid object pointer to header.
Missing: Binary representation of Derived reachable through point to Base (no static_cast usage!)
Do we need that std::launder? Yes, formally we do as this reinterpret_cast contains two casts between not pointer-interconvertible object, being (1) the pointer to the array and the pointer to its first element (which seems to be the most trivial one in the full discussion) and (2) the pointer to the binary representation of Header and the pointer to the object Header and the fact that header is standard layout does not change anything!
Base::getData
See Base::getHeader with the sole addition that we are allowed to do the given pointer arithmetic (for 0<=idx and idx<=Size) as the given pointer points to the first element of the array data and the full array data is reachable through the pointer (see above).
Done.
Why do you need this discussion
Certification of a compiler ensures that we can rely on it doing what the standard says (and nothing more). By the above, the standard says that we are allowed to do this stuff.
Why do you need this construction
Get a reference to a non-trivial container (eg list, map) to a static memory buffer without
containing the size of the container in the type - otherwise, we had quite a lot of templates -,
undefined behaviour,
indirect usage of the storage by using a (possibly temporary) api-class storing a pointer to header and data, as this will shift the problem to the users of the container casts,
user-defined casts applied by the user of the container class as they will raise a warning for users of our base library,
staying standard layout (yes, this is only the case if Header and the type stored in the buffer are standard layout, too),
without using pointers.
The latter two being needed as the structure is sent around via ipc.
²: Yes, reinterpret_cast of a pointer type to another pointer type does not change the value. Everybody assumes that, but it is also in the standard ([expr.static.cast].13):
Blockquote 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, the pointer value is unchanged by the conversion.
That shows that static_cast<T*>(static_cast<void*>(u)) does not change the pointer and by [expr.reinterpret.cast].7 this is equivalent to the corresponding reinterpret_cast
If I have a union.
struct None {};
template<typename T>
union Storage {
/* Ctors and Methods */
T obj;
None none;
};
pointer-interconvertible types means it is legal to perform the following conversion:
Storage<T> value(/* ctor args */);
T* obj = static_cast<T*>(static_cast<void*>(&value));
It is legal to treat an array of Storage<T> as an array of T?
Storage<T> values[20] = { /* initialisation */ };
T* objs = static_cast<T*>(static_cast<void*>(values));
for(auto i = 0; i < 20; ++i) {
objs[i].method(); // Is this pointer access legal?
}
No, it is not legal. The only thing that may be treated as an array with regards to pointer-arithmetic is an array (and the hypothetical single-element array formed by an object that is not element of an array). So the "array" relevant to the pointer arithmetic in objs[i] here is the hypothetical single-element array formed by obj of the first array element, since it is not itself element of an array. For i >= 1, objs[i] will not point to an object and so method may not be called on it.
Practically, there will be an issue in particular if T's size and the size of the union don't coincide, since even the arithmetic on the addresses will be off in this case. There is no guarantee that the two sizes coincide (even if None has sizeof and alignof equal to 1).
Aside from that issue, I doubt that compilers actually make use of this undefined behavior for optimization purposes. I can't guarantee it though.
Also note that you are only allowed to access obj through the pointer obtained by the cast if obj is the active member of the union, meaning that obj is the member which was initialized in the example.
You indicate that you intend to use this in a constant expression, in which case the compiler is required to diagnose the undefined behavior and is likely to reject such a program, regardless of the practical considerations about the optimizer.
Also, in a constant expression a cast from void* to a different object type (or a reinterpret_cast) is not allowed. So static_cast<T*>(static_cast<void*>(values)); will cause that to fail anyway. Although that is simply remedied by just taking a pointer to the union member directly (e.g. &values[0].obj). There is no reason to use the casts here.
Let S be a struct type which contains a character array data which has the maximum alignment and a fixed size. The idea is that S is able to store any object of type T whose size does not exceed the limit and which is trivially-copy-constructible and trivially-destructible.
static constexpr std::size_t MaxSize = 16;
struct S {
alignas(alignof(std::max_align_t)) char data[MaxSize];
};
Placement-new is used to construct an object of type T into the character array of a new S object. This object is then copied any number of times, including being returned and passed by value.
template <typename T>
S wrap(T t) {
static_assert(sizeof(T) <= MaxSize, "");
static_assert(std::is_trivially_copy_constructible_v<T>, "");
static_assert(std::is_trivially_destructible_v<T>, "");
S s;
new(reinterpret_cast<T *>(s.data)) T(t);
return s;
}
Later given a copy of this S value, reinterpret_cast is used to obtain T* from the pointer to the start of the character array, and then the T object is accessed in some way. The T type is the same as when the value was created.
void access(S s) {
T *t = reinterpret_cast<T *>(s.data);
t->print();
}
I would like to know if there is any undefined behavior involved in this scheme and how it would be solved. For instance, I am worried about:
Is there a problem with "reusing object storage", i.e. the problem that std::launder is designed to solve? I am not sure if it is valid to access data as a character array after constructing an instance of T there. Would I need std::launder in the place where the value is accessed, and why?
Is there a problem in the generated copy constructor of S which copies all the bytes in data, because some bytes might not have been initialized? I am worried both about bytes beyond sizeof(T) as well as possibly uninitialized bytes within the T object (e.g. padding).
My use case for this is implementation of a very lightweight polymorphic function wrapper which is able to be used with any callable satisfying those requirements that I have listed for T.
template<class T>
T* laundry_pod(void* ptr){
char buff[sizeof(T)];
std::memcpy(buff, ptr, sizeof(T));
auto* r=::new(ptr)T;
std::memcpy(ptr, buff, sizeof(T));
return r;
}
this may be of use. It converts the compilers interpretation of data at a location from one pod type to another, and compiles to a noop under optimization.
So take your buffer, laundry pod it to T, write to it, laundry pod back to gdneric storage. To read, laundry pod to T. Note that all writes through T pointer should be followed by laundering it back to the generic storage to avoid aliasing rules being used to optimize away the writes on the seemingly discarded ptr.
Does the following code violate strict aliasing or otherwise result in undefined behaviour according to the C++11 standard? Are there better ways to achieve the same functionality?
void do_things(const std::array<char, 64> &block) {
// ...
}
int main() {
std::vector<char> buffer(64);
do_things(reinterpret_cast<const std::array<char, 64> &>(buffer[0]));
}
tldr: Using const char * is a lot less painful
edit: since sizeof(std::array<char, n>) isn't guaranteed to be equal to n, I then propose the following:
void do_things(const char (&block)[64]) {
// ...
}
int main() {
std::vector<char> buffer(64);
do_things(reinterpret_cast<char (&)[64]>(buffer[0]));
}
According my understanding of aliasing this should not result in undefined behaviour and capture the semantics of passing an array of fixed size. Is my understanding correct?
The strict aliasing rule refers to §3.10 [basic.lval]/p10, which provides that
If a program attempts to access the stored value of an object through
a glvalue of other than one of the following types the behavior is
undefined:
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 4.4) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including,
recursively, an element or non-static data member of a subaggregate or
contained union)
[...]
Thus, accessing an object of type char through a glvalue of type std::array<char, N> does not break this rule, because std::array<char, N> is an aggregate type that includes char as an element of a nonstatic subaggregate data member.
However, you still can't do anything useful with the resulting reference without invoking undefined behavior because of a different rule - §9.3.1 [class.mfct.non-static]/p2:
If a non-static member function of a class X is called for an object
that is not of type X, or of a type derived from X, the behavior
is undefined.
It's also worth noting that no rule in the standard guarantees that sizeof(std::array<T, N>) == sizeof(T) * N. The only things the standard guarantees is that std::array<T, N> is an aggregate type and it can be initialized using a braced-init-list containing up to N Ts. The implementation is free to add extra stuff.
Depending on what do_things needs, you may want to make your function take random access iterators, or simply a pointer. Alternatively, if you want to limit your function to take only std::vector and std::arrays, you can write overloads that take const refs to those and call a helper function taking const char * that does the actual work.
The new version doesn't break any rule I can think of, but it's fairly bad design to require reinterpret_cast to be used pretty much every time your function is called. If you accidentally declared buffer as a std::vector<std::string>, or wrote buffer instead of buffer[0], the compiler will happily compile your code without a warning, with potentially disastrous results.
I'm trying to build a class template that packs a bunch of types in a suitably large char array, and allows access to the data as individual correctly typed references. Now, according to the standard this can lead to strict-aliasing violation, and hence undefined behavior, as we're accessing the char[] data via an object that is not compatible with it. Specifically, the standard states:
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 4.4) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char or unsigned char type.
Given the wording of the highlighted bullet point, I came up with the following alias_cast idea:
#include <iostream>
#include <type_traits>
template <typename T>
T alias_cast(void *p) {
typedef typename std::remove_reference<T>::type BaseType;
union UT {
BaseType t;
};
return reinterpret_cast<UT*>(p)->t;
}
template <typename T, typename U>
class Data {
union {
long align_;
char data_[sizeof(T) + sizeof(U)];
};
public:
Data(T t = T(), U u = U()) { first() = t; second() = u; }
T& first() { return alias_cast<T&>(data_); }
U& second() { return alias_cast<U&>(data_ + sizeof(T)); }
};
int main() {
Data<int, unsigned short> test;
test.first() = 0xdead;
test.second() = 0xbeef;
std::cout << test.first() << ", " << test.second() << "\n";
return 0;
}
(The above test code, especially the Data class is just a dumbed-down demonstration of the idea, so please don't point out how I should use std::pair or std::tuple. The alias_cast template should also be extended to handle cv qualified types and it can only be safely used if the alignment requirements are met, but I hope this snippet is enough to demonstrate the idea.)
This trick silences the warnings by g++ (when compiled with g++ -std=c++11 -Wall -Wextra -O2 -fstrict-aliasing -Wstrict-aliasing), and the code works, but is this really a valid way of telling the compiler to skip strict-aliasing based optimizations?
If it's not valid, then how would one go about implementing a char array based generic storage class like this without violating the aliasing rules?
Edit:
replacing the alias_cast with a simple reinterpret_cast like this:
T& first() { return reinterpret_cast<T&>(*(data_ + 0)); }
U& second() { return reinterpret_cast<U&>(*(data_ + sizeof(T))); }
produces the following warning when compiled with g++:
aliastest-so-1.cpp: In instantiation of ‘T& Data::first() [with
T = int; U = short unsigned int]’: aliastest-so-1.cpp:28:16:
required from here aliastest-so-1.cpp:21:58: warning: dereferencing
type-punned pointer will break strict-aliasing rules
[-Wstrict-aliasing]
Using a union is almost never a good idea if you want to stick with strict conformance, they have stringent rules when it comes to reading the active member (and this one only). Although it has to be said that implementations like to use unions as hooks for reliable behaviour, and perhaps that is what you are after. If that is the case I defer to Mike Acton who has written a nice (and long) article on aliasing rules, where he does comment on casting through a union.
To the best of my knowledge this is how you should deal with arrays of char types as storage:
// char or unsigned char are both acceptable
alignas(alignof(T)) unsigned char storage[sizeof(T)];
::new (&storage) T;
T* p = static_cast<T*>(static_cast<void*>(&storage));
The reason this is defined to work is that T is the dynamic type of the object here. The storage was reused when the new expression created the T object, which operation implicitly ended the lifetime of storage (which happens trivially as unsigned char is a, well, trivial type).
You can still use e.g. storage[0] to read the bytes of the object as this is reading the object value through a glvalue of unsigned char type, one of the listed explicit exceptions. If on the other hand storage were of a different yet still trivial element type, you could still make the above snippet work but would not be able to do storage[0].
The final piece to make the snippet sensible is the pointer conversion. Note that reinterpret_cast is not suitable in the general case. It can be valid given that T is standard-layout (there are additional restrictions on alignment, too), but if that is the case then using reinterpret_cast would be equivalent to static_casting via void like I did. It makes more sense to use that form directly in the first place, especially considering the use of storage happens a lot in generic contexts. In any case converting to and from void is one of the standard conversions (with a well-defined meaning), and you want static_cast for those.
If you are worried at all about the pointer conversions (which is the weakest link in my opinion, and not the argument about storage reuse), then an alternative is to do
T* p = ::new (&storage) T;
which costs an additional pointer in storage if you want to keep track of it.
I heartily recommend the use of std::aligned_storage.