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

Object to member conversions

49 views
Skip to first unread message

Piotr Wyderski

unread,
Aug 29, 2016, 3:14:01 AM8/29/16
to
Let there be an arithmetic type T and a class C { T t; };
Under what circumstances the conversions const T* <=>
const C* can be considered legal? Obviously it is not
so if C contains virtual methods. Is sizeof(T) == sizeof(C)
enough from the standard's point of view? Do classes having
this property have a special name/traits in C++ (including C++17)?

The application: I have a really big collection of Ts stored on a disk,
but it would be most convenient to access them via the object-oriented
"view" C.

Best regards, Piotr

Alf P. Steinbach

unread,
Aug 29, 2016, 4:47:14 AM8/29/16
to
On 29.08.2016 09:13, Piotr Wyderski wrote:
> Let there be an arithmetic type T and a class C { T t; };
> Under what circumstances the conversions const T* <=>
> const C* can be considered legal? Obviously it is not
> so if C contains virtual methods. Is sizeof(T) == sizeof(C)
> enough from the standard's point of view?

No, size has nothing to do with it.

Given a C object, the requirement is simply that `t` is the first data
member.


> Do classes having
> this property have a special name/traits in C++ (including C++17)?

Yep, they're called "standard layout" classes.

C++11 §9.2/20:
“A pointer to a standard-layout struct object, suitably converted using
a reinterpret_cast, points to its initial member (or if that member is a
bit-field, then to the unit in which it resides) and vice versa.”

Note that this requires that the object is originally a C.


> The application: I have a really big collection of Ts stored on a disk,
> but it would be most convenient to access them via the object-oriented
> "view" C.

Well, on disk is no problem: just read them into C instances' t members.
No need to do pointer casting.


Cheers & hth.,

- Alf


Paavo Helde

unread,
Aug 29, 2016, 6:33:29 AM8/29/16
to
On 29.08.2016 11:46, Alf P. Steinbach wrote:
> On 29.08.2016 09:13, Piotr Wyderski wrote:
>> Let there be an arithmetic type T and a class C { T t; };
>> Under what circumstances the conversions const T* <=>
>> const C* can be considered legal? Obviously it is not
>> so if C contains virtual methods. Is sizeof(T) == sizeof(C)
>> enough from the standard's point of view?
>
> No, size has nothing to do with it.
>
> Given a C object, the requirement is simply that `t` is the first data
> member.

Later he is talking about arrays of T, so it appears the above const T*
really refers to an array, not to a single value. And in arrays sizeof
is very relevant - sizeof is basically defined as the element spacing in
an array.

>
>> Do classes having
>> this property have a special name/traits in C++ (including C++17)?
>
> Yep, they're called "standard layout" classes.
>
> C++11 §9.2/20:
> “A pointer to a standard-layout struct object, suitably converted using
> a reinterpret_cast, points to its initial member (or if that member is a
> bit-field, then to the unit in which it resides) and vice versa.”
>
> Note that this requires that the object is originally a C.

So it appears that formally casting a T* to a C* is still undefined. I
gather that a perverse implementation might pad T and C in the end to
have equal sizeof-s, but fill the padding with different values which
are checked at run time.

>
>> The application: I have a really big collection of Ts stored on a disk,
>> but it would be most convenient to access them via the object-oriented
>> "view" C.
>
> Well, on disk is no problem: just read them into C instances' t members.
> No need to do pointer casting.

He says that the arrays are really large, so he might think of something
like memory mapping the binary file and interpret the content as const
C* instead of const T*. If so, then memory mapping and cast to 'const
T*' are already non-portable enough so that additional cast to const C*
would probably be the safest step here (assuming standard layout, T as
the first member and equal sizeof-s).


Piotr Wyderski

unread,
Aug 30, 2016, 3:26:08 AM8/30/16
to
Paavo Helde wrote:

> He says that the arrays are really large, so he might think of something
> like memory mapping the binary file and interpret the content as const
> C* instead of const T*. If so, then memory mapping and cast to 'const
> T*' are already non-portable enough so that additional cast to const C*
> would probably be the safest step here (assuming standard layout, T as
> the first member and equal sizeof-s).

Yes, you understood the use case correctly. It is all about bulk loading
-- I want to load a big chunk of disk data (not even memory-mapping the
file, full-blown asynchronous IO operations will be used instead) and
then apply some non-mutating operations on this array of Ts as if it
were an array of Cs. In the moment of content creation it was an array
of Cs, so technically it is a sequence of reinterpret_casts
C[]=>T[]=>C[], which just happened in different runs of the same
program. The content does not have to be portable, it will never leave
the machine. I just wanted it to be as C++-conforming as possible.
Since C++ appears to know something about the standard_layout
peculiarities, it seems doable without excessive violations of the standard.

Best regards, Piotr



Öö Tiib

unread,
Aug 30, 2016, 6:30:14 AM8/30/16
to
Add something like that into code after that struct C is defined:

static_assert( std::is_standard_layout<C>::value
&& std::is_arithmetic<T>::value
&& std::is_same<T, decltype(C::t)>::value
&& sizeof (T[42]) == sizeof (C[42])
, "need to cast between T[] and C[]" );

That double-checks the situation in paranoid manner and so documents
why it is important and makes sure that some future maintenance does
not accidentally break it. Files on disk tend to live longer than versions
of software.

Chris Vine

unread,
Aug 30, 2016, 7:28:31 AM8/30/16
to
This looks like a very bad idea. If what you have committed to disk
is a binary representation of an array of T objects of some kind, you
cannot recreate your C objects from this in a portable fashion because
the C objects no longer exist. You could only do the reverse portably.
The operative word is "portably" - if sizeof C is the same as sizeof T
you will probably get away with it.

By virtue of the sixth bullet of §3.10/10 read with §9.2/20, given
a type T which enables struct C { T t; int i;}; to be both an aggregate
and a standard layout struct, and an object of type T:

T t;

what you can do on my reading of the standard is this:

C* c = reinterpret_cast<C*>(&t); // OK so far, but be very afraid
T t2 = c->t; // this is all you can do with C

But there may be something else in the standard which precludes even
this.

Obviously this would not allow you to treat your array of T objects as
if it were an array of C objects.

The correct thing to do is to deal with your T objects as T objects.

Chris

mark

unread,
Aug 30, 2016, 11:23:48 AM8/30/16
to
On 2016-08-30 13:28, Chris Vine wrote:
>
> By virtue of the sixth bullet of §3.10/10 read with §9.2/20, given
> a type T which enables struct C { T t; int i;}; to be both an aggregate
> and a standard layout struct, and an object of type T:
>
> T t;
>
> what you can do on my reading of the standard is this:
>
> C* c = reinterpret_cast<C*>(&t); // OK so far, but be very afraid
> T t2 = c->t; // this is all you can do with C
>
> But there may be something else in the standard which precludes even
> this.

My reading of the standard is that this works if C (and all its members)
has a trivial default constructor. If not, it's undefined behavior. If C
has a trivial default constructor, storage allocation is enough to start
the lifetime, otherwise a constructor has to run.

§3.08/5 Object lifetime:
<<<
Before the lifetime of an object has started but after the storage which
the object will occupy has been allocated or, after the lifetime of an
object has ended and before the storage which the object occupied is
reused or released, any pointer that refers to the storage location
where the object will be or was located may be used but only in limited
ways. [...] Indirection through such a pointer is permitted but the
resulting lvalue may only be used in limited ways, as described below.
The program has undefined behavior if:
[...]
- the pointer is used to access a non-static data member or call a
non-static member function of the object, or
[...]
>>>
0 new messages