Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Union type punning in C++ redux

177 views
Skip to first unread message

Daniel

unread,
Feb 18, 2020, 11:40:59 PM2/18/20
to
I've been following the "Union type punning in C++" posts with some interest,
but not exactly sure of the conclusion.

Would the following be legal C++?

#include <string>
#include <new>

// pod type
struct A
{
uint8_t tag;
};

// non-pod
struct B
{
A a;
std::string s;

B(const std::string& s)
: a{1}, s(s)
{

}
};

struct C
{
A a;
double d;

C(double d)
: a{ 2 }, d(d)
{

}
};

class V
{
union
{
A a;
B b;
C c;
};

public:
V(const std::string& s)
{
::new(&b) B(s);
}
V(double d)
{
::new(&c) C(d);
}
~V()
{
switch (tag())
{
case 1:
b.~B();
break;
case 2:
c.~C();
break;
default:
break;
}
}
uint8_t tag() const
{
return a.tag;
}
};

What if B and C were defined through inheritance from A instead, i.e.

struct B : A
{
std::string s;

B(const std::string& s)
: A{1}, s(s)
{

}
};


struct C : A
{
double d;

C(double d)
: A{ 2 }, d(d)
{

}
};

Thanks,
Daniel

Pavel

unread,
Feb 19, 2020, 1:21:36 AM2/19/20
to
I think no: it is not allowed to inspect a.tag regardless of the constructor
used to construct V because `tag' is not a part of the common initial sequence
of either V::a and V::b or of V::a and V::c (I think the common initial sequence
is empty in both cases).
>
> What if B and C were defined through inheritance from A instead, i.e.
>
> struct B : A
> {
> std::string s;
>
> B(const std::string& s)
> : A{1}, s(s)
> {
>
> }
> };
>
>
> struct C : A
> {
> double d;
>
> C(double d)
> : A{ 2 }, d(d)
> {
>
> }
> };
I think no, for same reason.

I think changing implementation of tag() to either of { return b.tag; } or {
return c.tag; } would make the code valid (then of course having V::a member
would be unnecessary).
>
> Thanks,
> Daniel
>

FWIW,

-Pavel

Daniel

unread,
Feb 19, 2020, 9:10:23 AM2/19/20
to
On Wednesday, February 19, 2020 at 1:21:36 AM UTC-5, Pavel wrote:
> Daniel wrote:
> >
> > Would the following be legal C++?
> >
> > snipped
> >
> I think no
>
Would the following (non-union) alternative be legal C++?

#include <string>
#include <new>
#include <algorithm>

enum class tag_type : uint8_t {b,c};

struct A
{
tag_type tag;
};

struct B : A
{
uint8_t extra;
uint64_t n;

B(uint64_t n)
: A{tag_type::b}, n(n)
{

}
};

struct C : A
{
double d;

C(double d)
: A{tag_type::c}, d(d)
{

}
};

class V
{
static constexpr size_t data_size = std::max(sizeof(B), sizeof(C));
static constexpr size_t data_align = std::max(alignof(B), alignof(C));

typedef typename std::aligned_storage<data_size, data_align>::type data_t;

data_t data_;

public:
V(uint8_t n)
{
::new(&data_) B(n);
}
V(double d)
{
::new(&data_) B(d);
}
~V()
{
switch (tag())
{
case tag_type::b:
reinterpret_cast<const B*>(&data_)->~B();
break;
case tag_type::c:
reinterpret_cast<const C*>(&data_)->~C();
break;
default:
break;
}
}
tag_type tag() const
{
return reinterpret_cast<const A*>(&data_)->tag;
}
};


Öö Tiib

unread,
Feb 19, 2020, 11:26:22 AM2/19/20
to
On Wednesday, 19 February 2020 16:10:23 UTC+2, Daniel wrote:
> On Wednesday, February 19, 2020 at 1:21:36 AM UTC-5, Pavel wrote:
> > Daniel wrote:
> > >
> > > Would the following be legal C++?
> > >
> > > snipped
> > >
> > I think no
> >
> Would the following (non-union) alternative be legal C++?

No. You have wrong idea that base classes are better.

Resulting are not standard layout types. It is because
the requirement (in [class.prop]) "has all non-static data
members and bit-fields in the class and its base classes
first declared in the same class" is not fulfilled.

And so "common initial sequence" does not apply and
"address of class object is same as address of its first
non-static data member object" does not also apply.
I suggest to get rid of base classes with data members
and have A as first data member. Then it is proper
approach.

Additional note:
When you want to have standard library classes as data members of
your standard layout classes then always static_assert in code
that these are:

static_assert(std::is_standard_layout<std::string>::value
, "This code needs std::string to be standard layout");

It is because standard does not require it and that can turn
your reinterpret_cast of pointer of object into pointer of its
first member into undefined behavior.

Daniel

unread,
Feb 19, 2020, 12:09:20 PM2/19/20
to
On Wednesday, February 19, 2020 at 11:26:22 AM UTC-5, Öö Tiib wrote:
> On Wednesday, 19 February 2020 16:10:23 UTC+2, Daniel wrote:
> > >
> > Would the following (non-union) alternative be legal C++?
>
> <snipped>
>
> No.
>
> <snipped>
>
That's very helpful, thanks.

For this exercise, the design goals are correctness, compactness (assume 10's
of millions of V's), and encapsulation of the B's and C's, in that order. For
the last example, assuming eight byte alignment, it would be desirable to have
sizeof(V) == 16.

To that end, my next question is, would this be legal C++:

#include <string>
#include <new>
#include <algorithm>
#include <cstring>

struct B
{
uint8_t tag;
uint8_t extra;
uint64_t n;

B(uint64_t n, uint8_t extra = 0)
: tag{1}, n(n), extra(extra)
{

}
};

struct C
{
uint8_t tag;
double d;

C(double d)
: tag{2}, d(d)
{

}
};

class V
{
static constexpr size_t data_size = std::max(sizeof(B), sizeof(C));
static constexpr size_t data_align = std::max(alignof(B), alignof(C));

typedef typename std::aligned_storage<data_size, data_align>::type data_t;

data_t data_;

public:
V(uint8_t n)
{
::new(&data_) B(n);
}
V(double d)
{
::new(&data_) B(d);
}
~V()
{
switch (tag())
{
case 1:
reinterpret_cast<const B*>(&data_)->~B();
break;
case 2:
reinterpret_cast<const C*>(&data_)->~C();
break;
default:
break;
}
}
uint8_t tag() const
{
uint8_t t;
std::memcpy(&t, &data_, sizeof(uint8_t));
return t;
}
};

> Additional note:
> When you want to have standard library classes as data members of
> your standard layout classes then always static_assert in code
> that these are:
>
> static_assert(std::is_standard_layout<std::string>::value
> , "This code needs std::string to be standard layout");
>
> It is because standard does not require it and that can turn
> your reinterpret_cast of pointer of object into pointer of its
> first member into undefined behavior.

Thanks for pointing that out. For my purposes, I do need to support

std::allocator_traits<Alloc>::pointer

including fancy pointers.

Daniel

Öö Tiib

unread,
Feb 19, 2020, 3:22:52 PM2/19/20
to
Why const B* not B*?

> break;
> case 2:
> reinterpret_cast<const C*>(&data_)->~C();
> break;
> default:
> break;
> }
> }
> uint8_t tag() const
> {
> uint8_t t;
> std::memcpy(&t, &data_, sizeof(uint8_t));
> return t;
> }
> };

Yes, the classes are standard layout and therefore in tag()
you could just return *reinterpret_cast<uint8_t const*>(&data_);
as well.


> > Additional note:
> > When you want to have standard library classes as data members of
> > your standard layout classes then always static_assert in code
> > that these are:
> >
> > static_assert(std::is_standard_layout<std::string>::value
> > , "This code needs std::string to be standard layout");
> >
> > It is because standard does not require it and that can turn
> > your reinterpret_cast of pointer of object into pointer of its
> > first member into undefined behavior.
>
> Thanks for pointing that out. For my purposes, I do need to support
>
> std::allocator_traits<Alloc>::pointer
>
> including fancy pointers.
>
> Daniel

Yes, when you are unsure if certain member in your B or C
is standard layout or not then check std::is_standard_layout
about the member or about whole B or C.

Daniel

unread,
Feb 19, 2020, 3:59:39 PM2/19/20
to
On Wednesday, February 19, 2020 at 3:22:52 PM UTC-5, Öö Tiib wrote:
> On Wednesday, 19 February 2020 19:09:20 UTC+2, Daniel wrote:

> > ~V()
> > {
> > switch (tag())
> > {
> > case 1:
> > reinterpret_cast<const B*>(&data_)->~B();
> > break;
>
> Why const B* not B*?

No reason. Copied that piece from code that accesses the object.

>
> > case 2:
> > reinterpret_cast<const C*>(&data_)->~C();
> > break;
> > default:
> > break;
> > }
> > }
> > uint8_t tag() const
> > {
> > uint8_t t;
> > std::memcpy(&t, &data_, sizeof(uint8_t));
> > return t;
> > }
> > };
>
> Yes, the classes are standard layout and therefore in tag()
> you could just return *reinterpret_cast<uint8_t const*>(&data_);
> as well.
>
Thanks! very much appreciate your feedback.

Daniel

Cholo Lennon

unread,
Feb 19, 2020, 8:45:39 PM2/19/20
to
On 2/19/20 1:40 AM, Daniel wrote:
> I've been following the "Union type punning in C++" posts with some interest,
> but not exactly sure of the conclusion.

Just for the record: there is a nice talk from CppCon 2019 about "Type
punning in modern C++"

https://youtu.be/_qzMpk-22cc

--
Cholo Lennon
Bs.As.
ARG

Pavel

unread,
Feb 20, 2020, 1:08:54 AM2/20/20
to
I do not think this code has specified behavior as I could not find in the
Standard any additional guarantee about reinterpret_cast results conditioned on
the involved types' being having standard-layout.

The only thing that seem to be guaranteed about reinterpret_cast (except for
some cases of its application to the pointer to an object of class derived from
the class to which cast is done or other way around where it is guaranteed to
behave like static_cast) is that under certain conditions reverse cast of the
pointer received from the direct cast yields the original value of the pointer.

In other words, I think the result of reinterpret_cast of a pointer (with the
exception mentioned above) can only be useful to cast it back to the pointer to
the object of its original type. The above code relies on de-referenced result
of reinterpreted_cast so I think its behavior is unspecified.
>
>
>>> Additional note:
>>> When you want to have standard library classes as data members of
>>> your standard layout classes then always static_assert in code
>>> that these are:
>>>
>>> static_assert(std::is_standard_layout<std::string>::value
>>> , "This code needs std::string to be standard layout");
>>>
>>> It is because standard does not require it and that can turn
>>> your reinterpret_cast of pointer of object into pointer of its
>>> first member into undefined behavior.
>>
>> Thanks for pointing that out. For my purposes, I do need to support
>>
>> std::allocator_traits<Alloc>::pointer
>>
>> including fancy pointers.
>>
>> Daniel
>
> Yes, when you are unsure if certain member in your B or C
> is standard layout or not then check std::is_standard_layout
> about the member or about whole B or C.
>

HTH
-Pavel

Öö Tiib

unread,
Feb 20, 2020, 2:00:42 AM2/20/20
to
I copy-paste from C++2017:

| Two objects a and b are pointer-interconvertible if:
| —(4.1) they are the same object, or
| —(4.2) one is a standard-layout union object and the other is a non-static data member of that object, or
| —(4.3) one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object, or
| —(4.4) there exists an object c such that a and c are pointer-interconvertible, and c and b are pointerinterconvertible.
|
| If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast


> The only thing that seem to be guaranteed about reinterpret_cast (except for
> some cases of its application to the pointer to an object of class derived from
> the class to which cast is done or other way around where it is guaranteed to
> behave like static_cast) is that under certain conditions reverse cast of the
> pointer received from the direct cast yields the original value of the pointer.
>
> In other words, I think the result of reinterpret_cast of a pointer (with the
> exception mentioned above) can only be useful to cast it back to the pointer to
> the object of its original type. The above code relies on de-referenced result
> of reinterpreted_cast so I think its behavior is unspecified.

I am quite certain that the reinterpret_cast is not so useless as you put it.

Pavel

unread,
Feb 21, 2020, 2:03:16 AM2/21/20
to
BTW. 2020 draft n4849 changes "first" to "any" here
> base class subobject of that object, or
> | —(4.4) there exists an object c such that a and c are pointer-interconvertible, and c and b are pointerinterconvertible.
> |
> | If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast
Thanks, I missed this language. I think the only non-trivial addition to my
interpretation of the standard it brings is 4.3 (the guarantees given by 4.2 can
be taken advantage of with unions and no reinterpret_cast and the other 2 are
trivial); but I don't think it helps the legality of the latest OP's code
because it is not obvious to me which of these 4 rules can be applied to
conclude that &V::data_ and the results of its reinterpret-casting to const A*
or const B* are pointer-interconvertible.
>
>
>> The only thing that seem to be guaranteed about reinterpret_cast (except for
>> some cases of its application to the pointer to an object of class derived from
>> the class to which cast is done or other way around where it is guaranteed to
>> behave like static_cast) is that under certain conditions reverse cast of the
>> pointer received from the direct cast yields the original value of the pointer.
>>
>> In other words, I think the result of reinterpret_cast of a pointer (with the
>> exception mentioned above) can only be useful to cast it back to the pointer to
>> the object of its original type. The above code relies on de-referenced result
>> of reinterpreted_cast so I think its behavior is unspecified.
>
> I am quite certain that the reinterpret_cast is not so useless as you put it.
Well, most of legal and useful uses I can remember were casting from void* to T*
and back; but your citation (specifically 4.3) does add one more potentially
useful legal use I do not remember using (but now that you mentioned it I think
I am recalling seeing it somewhere), namely the casts between a pointer to a
standard-layout class object and the pointer to its first member.

(It also says that for an "empty" standard-layout class object we can
reinterpret_cast between that object and any of its base objects (obviously,
also empty) -- but that is not very useful as it is both shorter and IMHO more
readable to just use static_cast for that).

Daniel

unread,
Feb 21, 2020, 9:05:37 AM2/21/20
to
On Friday, February 21, 2020 at 2:03:16 AM UTC-5, Pavel wrote:
> Öö Tiib wrote:

> > I am quite certain that the reinterpret_cast is not so useless as you put >> it.
> Well, most of legal and useful uses I can remember were casting from void*
> to T* and back; but your citation (specifically 4.3) does add one more
> potentially useful legal use I do not remember using (but now that you
> mentioned it I think I am recalling seeing it somewhere), namely the casts
> between a pointer to a standard-layout class object and the pointer to its
> first member.
>
Hardly normative, but by my count, boost 1_71 has 4414 occurrences of
reinterpret_cast in 268 header files, including many uses with aligned
storage.

Öö Tiib

unread,
Feb 21, 2020, 1:27:01 PM2/21/20
to
You totally misinterpreted extent of my example. It was only given
to show that "I could not find in the Standard any additional guarantee
about reinterpret_cast results conditioned on the involved types' being
having standard-layout" does not matter since I could. Search bit more.

> >
> >> The only thing that seem to be guaranteed about reinterpret_cast (except for
> >> some cases of its application to the pointer to an object of class derived from
> >> the class to which cast is done or other way around where it is guaranteed to
> >> behave like static_cast) is that under certain conditions reverse cast of the
> >> pointer received from the direct cast yields the original value of the pointer.
> >>
> >> In other words, I think the result of reinterpret_cast of a pointer (with the
> >> exception mentioned above) can only be useful to cast it back to the pointer to
> >> the object of its original type. The above code relies on de-referenced result
> >> of reinterpreted_cast so I think its behavior is unspecified.
> >
> > I am quite certain that the reinterpret_cast is not so useless as you put it.
> Well, most of legal and useful uses I can remember were casting from void* to T*
> and back; but your citation (specifically 4.3) does add one more potentially
> useful legal use I do not remember using (but now that you mentioned it I think
> I am recalling seeing it somewhere), namely the casts between a pointer to a
> standard-layout class object and the pointer to its first member.
>
> (It also says that for an "empty" standard-layout class object we can
> reinterpret_cast between that object and any of its base objects (obviously,
> also empty) -- but that is not very useful as it is both shorter and IMHO more
> readable to just use static_cast for that).

Wrong place, red herring. That is what is fun about standard, there are additional guarantees about bytes (IOW chars) and also about that
std::aligned_storage. ;)

Pavel

unread,
Feb 23, 2020, 12:13:43 AM2/23/20
to
I could not possibly "misinterpret the extent" of your example because my answer
to your answer did not try to interpret its extent. All my answer to your answer
said was:

1. That your answer pointed out one legal and potentially useful use of
reinterpret_cast that I did not know before.
2. That your answer did not support your opinion of the legality of the latest
code of the OP.

Search bit more.
Thank you, I did and still did not see anything that would help in proving that
the reinterpret_casts used in the OP's last version of the code were legal. Did you?

>
>>>
>>>> The only thing that seem to be guaranteed about reinterpret_cast (except for
>>>> some cases of its application to the pointer to an object of class derived from
>>>> the class to which cast is done or other way around where it is guaranteed to
>>>> behave like static_cast) is that under certain conditions reverse cast of the
>>>> pointer received from the direct cast yields the original value of the pointer.
>>>>
>>>> In other words, I think the result of reinterpret_cast of a pointer (with the
>>>> exception mentioned above) can only be useful to cast it back to the pointer to
>>>> the object of its original type. The above code relies on de-referenced result
>>>> of reinterpreted_cast so I think its behavior is unspecified.
>>>
>>> I am quite certain that the reinterpret_cast is not so useless as you put it.
>> Well, most of legal and useful uses I can remember were casting from void* to T*
>> and back; but your citation (specifically 4.3) does add one more potentially
>> useful legal use I do not remember using (but now that you mentioned it I think
>> I am recalling seeing it somewhere), namely the casts between a pointer to a
>> standard-layout class object and the pointer to its first member.
>>
>> (It also says that for an "empty" standard-layout class object we can
>> reinterpret_cast between that object and any of its base objects (obviously,
>> also empty) -- but that is not very useful as it is both shorter and IMHO more
>> readable to just use static_cast for that).
>
> Wrong place, red herring.
??
> That is what is fun about standard, there are additional guarantees about bytes (IOW chars) and also about that
> std::aligned_storage. ;)
There is nothing special I can see about std::aligned_storage with regard to
reinterpret_cast. That said, I am always glad to learn from people who are know
more than I do. ;)

Pavel

unread,
Feb 23, 2020, 2:04:15 AM2/23/20
to
We could discuss few specific ones that you believe are most common or useful
and try to figure out whether they are legal C++ and, if not, how they can be
fixed.

As for your last example, the following seems to be a variant class that has
same public API as yours but is IMHO legal C++ (I omitted the definitions of B
and C as they did not need any changes):

class V2 {
union {
B b;
C c;
};
public:
V2(uint64_t n): b(n) { }
V2(double d): c(d) { }
~V2() {
switch (tag()) {
case 1:
b.~B();
break;
case 2:
c.~C();
break;
default:
break;
}
}
uint8_t tag() const { return b.tag; }
};

Above, it is legal to call access `b.tag' regardless of which of b or c is the
active member of the union because `tag' is a part of common initial sequence of
B and C as defined in 11.4-25 of the Standard.

V2 also fulfills your requirement of having sizeof of 16 assuming 8-bytes alignment.

Daniel

unread,
Feb 23, 2020, 10:43:50 AM2/23/20
to
On Sunday, February 23, 2020 at 12:13:43 AM UTC-5, Pavel wrote:
> Öö Tiib wrote:
> > std::aligned_storage. ;)
> There is nothing special I can see about std::aligned_storage with regard to
> reinterpret_cast.

Every use I've seen of std::aligned_storage has reinterpret_cast, without
reinterpret_cast, what could you do with it? For instance, the example in
cppreference,

template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
std::size_t m_size = 0;

public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};

// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
}

// Access an object in aligned storage
const T& operator[](std::size_t pos) const
{
// note: needs std::launder as of C++17
return *reinterpret_cast<const T*>(&data[pos]);
}

// Delete objects from aligned storage
~static_vector()
{
for(std::size_t pos = 0; pos < m_size; ++pos) {
// note: needs std::launder as of C++17
reinterpret_cast<T*>(&data[pos])->~T();
}
}
};

I note the comment in the destructor, "needs std::launder as of C++17",
which I wasn't previously aware of.

Daniel

Daniel

unread,
Feb 23, 2020, 10:47:05 AM2/23/20
to
That would do, thanks,
Daniel

Pavel

unread,
Feb 24, 2020, 1:09:30 AM2/24/20
to
Daniel wrote:
> On Sunday, February 23, 2020 at 12:13:43 AM UTC-5, Pavel wrote:
>> Öö Tiib wrote:
>>> std::aligned_storage. ;)
>> There is nothing special I can see about std::aligned_storage with regard to
>> reinterpret_cast.
>
> Every use I've seen of std::aligned_storage has reinterpret_cast, without
> reinterpret_cast, what could you do with it?

It is difficult to say. Somehow the Standard carefully avoids ever guaranteeing
anything about the result of reinterpret_cast<T1*>(tPtr); where tPtr is of type
T* and T is different from T1.

E.g. looking at 7.6.1.9 "Renterpret Cast" in n4849 (and, I believe all previous
Standards):

3 [Note: The mapping performed by reinterpret_cast might, or might not, produce
a representation different from the original value. — end note]
...

You would think that somewhere in

7 An object pointer can be explicitly converted to an object pointer of a
different type... When a prvalue v of object pointer type is converted to the
object pointer type “pointer to cv T”, the result is static_cast<cv
T*>(static_cast<cv void*>(v)). [Note: Converting a prvalue of type “pointer to
T1” to the type “pointer
to T2” (where T1 and T2 are object types and where the alignment requirements of
T2 are no stricter than
those of T1) and back to its original type yields the original pointer value. —
end note]

the Standard would guarantee something about memory addresses like "the address
of v and the address represented by the result of reinterpret_cast<T2*>(v) are
same" but alas. The guarantees are only for some two-way applications e.g. T* to
intptr_t (see 7.6.1.9-5) and back or T1 to T2 and back.

Of course, everybody uses static_cast and reinterpret_cast for this purpose so
it probably "will just work". Wherever possible I would, however, stick to union
approach which seems to be better defined.
It is probably prudent to use it even though it may be important only when T has
some special features, see 17.6.4 "Pointer optimization barrier":

5 [Note: If a new object is created in storage occupied by an existing object of
the same type, a pointer
to the original object can be used to refer to the new object unless its
complete object is a const object
or it is a base class subobject; in the latter cases, this function can be used
to obtain a usable pointer
to the new object. ...]

>
> Daniel
>

Öö Tiib

unread,
Feb 24, 2020, 2:38:06 AM2/24/20
to
So you could find place where standard says that reinterpret_cast<T*>(v)
is same as static_cast<T*>(static_cast<void*>(v))?

May be you cant find that?:
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 (6.9.2) with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

About first member of standard layout struct being pointer-interconvertible
to struct itself I already copy pasted. And aligned_storage
is satisfying the alignment requirements of T so why you deny
that Daniel's example is well-formed?

Öö Tiib

unread,
Feb 24, 2020, 2:43:49 AM2/24/20
to
On Monday, 24 February 2020 09:38:06 UTC+2, Öö Tiib wrote:
>
> About first member of standard layout struct being pointer-interconvertible
> to struct itself I already copy pasted. And aligned_storage
> is satisfying the alignment requirements of T so why you deny
> that Daniel's example is well-formed?

I meant well-defined.

Pavel

unread,
Feb 25, 2020, 12:04:08 AM2/25/20
to
Because the above text requires that "there is an object b of type T (ignoring
cv-qualification) that is pointer-interconvertible (6.9.2) with a" for result to
be a pointer to b.

How can we deduce that the type std::aligned_storage<T,
some-align-greater-than-or-equal-to-alignof<T> >::type is
pointer-interconvertible with T (even ignoring cv-qualification)?

Pavel

unread,
Feb 25, 2020, 12:11:23 AM2/25/20
to
More precisely I should have asked: \

How can we deduce that *an object of type* std::aligned_storage<T,
some-align-greater-than-or-equal-to-alignof<T> >::type is
pointer-interconvertible with *an object of type* T (even ignoring

Pavel

unread,
Feb 25, 2020, 12:19:18 AM2/25/20
to
Thanks, it is a nice and somewhat funny talk. Among the others, there is a
chance that the OP's code will become legal in C++ 23 :-).

The guy is in "reinterpret_cast" camp but in the Q&A session he at least
acknowledges the existence of the "union" camp that believes that no use of
reinterpret_cast is legal for type punning. It seems someone from that other
camp wrote an article to disprove reinterpret_cast type punning .. funny I never
heard of it; will try to find and read it when idle i.e. probably also around
2023 when it really does not matter :-).

>
> --
> Cholo Lennon
> Bs.As.
> ARG
>

-Pavel

Chris M. Thomasson

unread,
Feb 25, 2020, 1:38:09 AM2/25/20
to
Fwiw, what I get from the talk is that its better have a ctor and dtor,
or else its undefined regardless? Placement new and explicit dtor call
for properly aligned bytes.

Pavel

unread,
Feb 26, 2020, 1:07:52 AM2/26/20
to
My understanding of it was that the object should be created so constructor
should be called as part of that (the constructor implicitly defined by the
implementation should do, too) before using it (to put it in another way, no
amount of pointer casting and arithmetic will help to make the code using the
resulting pointer well defined if an object to which the pointer is trying to
point is not created)
0 new messages