Does a phantom type have the same alignment as the original one?
Asked Answered
S

3

27

Consider the following struct that contains some environment values:

struct environment_values {
  uint16_t humidity;
  uint16_t temperature;
  uint16_t charging;
};

I would like to add some additional information to those values with a phantom type* and make their types distinct at the same time:

template <typename T, typename P>
struct Tagged {
    T value;
};

// Actual implementation will contain some more features
struct Celsius{};
struct Power{};
struct Percent{};

struct Environment {
  Tagged<uint16_t,Percent> humidity;
  Tagged<uint16_t,Celsius> temperature;
  Tagged<uint16_t,Power>   charging;
};

Is the memory-layout of Environment the same as environment_values? Does this also hold for mixed type layouts, e.g.:

struct foo {
    uint16_t value1;
    uint8_t  value2;
    uint64_t value3;
}

struct Foo {
    Tagged<uint16_t, Foo>  Value1;
    Tagged<uint8_t , Bar>  Value2;
    Tagged<uint64_t, Quux> Value3;
}

For all types I've tried so far, the following assertions held:

template <typename T, typename P = int>
constexpr void check() {
    static_assert(alignof(T) == alignof(Tagged<T,P>), "alignment differs");
    static_assert(sizeof(T)  == sizeof(Tagged<T,P>),  "size differs");
}

// check<uint16_t>(), check<uint32_t>(), check<char>() …

Since the size of the tagged and untagged variants is also the same, I guess the answer should be yes, but I would like to have some certainty.

* I have no idea how those tagged values are called in C++. "Strongly typed typedefs"? I've taken the name from Haskell.

Shoddy answered 26/9, 2017 at 11:27 Comment(7)
Well, [basic.align] has An alignment is an implementation-defined integer value, so this is all implementation defined. I would expect this to work for any sane compiler and if you worried you could just assert that your assumptions are correct and if they ever fail it will fire.Essential
There is a guarantee that layout-compatible types have the same alignment ([basic.compound]/3), but I don't see anything saying that struct { T m; } and T are layout-compatible.Freestyle
Cue Star Wars themeMoonshine
Doing this. As long as you're on Itanium ABI & use for aggregates, you're fine.Evangelist
What exactly do you feel is missing from the answers provided that motivated a bounty? So I know what to focus on.Hurrah
What is the problem you're trying to solve? I've successfully created taggings by declaring (not defining) mem fns like Foo is(Field<0>); and then using ctor reflection to associate T Foo::is(Field<i>) with the fields.Evangelist
@Hurrah exactly what you've commented on gsamaras' answer. That was the missing part. I think it's just fair that you get the bounty.Shoddy
T
10

The Standard mentions in [basic.align]/1:

Object types have alignment requirements (3.9.1, 3.9.2) which place restrictions on the addresses at which an object of that type may be allocated. An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated. An object type imposes an alignment requirement on every object of that type; stricter alignment can be requested using the alignment specifier (7.6.2).

Moreover, [basic.compound]/3, mentions:

The value representation of pointer types is implementation-defined. Pointers to layout-compatible types shall have the same value representation and alignment requirements (6.11). [Note: Pointers to over-aligned types (6.11) have no special representation, but their range of valid values is restricted by the extended alignment requirement].

As a result, there is a guarantee that layout-compatible types have the same alignment.

struct { T m; } and T are not layout-compatible.

As pointed here, in order for two elements to be layout compatible then they both have to be standard-layout types, and their non-static data members must occur with the same types and in the same order.

struct { T m; } contains just a T, but T is a T so it cannot contain a T as its first non-static data member.

Turrell answered 26/9, 2017 at 12:16 Comment(1)
struct { T m; } and T are not layout-compatible. In order to be layout compatible then they both have to be standard-layout types, and their non-static data members must occur with the same types and in the same order. T might not be standard layout. T might not even be a class. But even if T does meet these requirements, struct { T m; } contains just a T, but T is a T so it cannot contain a T as its first non-static data member.Hurrah
O
7

According to the letter of the law, size and alignment of types is implementation-defined and the standard gives you few if any guarantees about what sizeof and alignof will return.

template <typename T, typename P>
struct Tagged {
    T value;
};

In theory, the compiler is permitted to add padding to the end of this struct, which would obviously alter the size and probably the alignment as well. In practise, the only time I could envisage this happening is if T was given some sort of compiler-specific "packed" attribute, but Tagged was not (but even then, GCC seems to work okay).

In any case, I'd say it would be a good idea to add some static asserts to ensure that the compiler is being sensible -- which is exactly what you've done :).

Ossieossietzky answered 26/9, 2017 at 12:25 Comment(1)
A compiler might choose to just use the same large alignment for all structs, just to keep it simple, adding padding accordingly...Irreverent
H
3

As mentioned by gsamaras, the standard guarantees the same alignment for layout compatible classes.

Unfortunately, struct { T m; } and T are not layout compatible.

In 12.2.21 the standard lays out the requirements for a layout compatible class:

Two standard-layout struct (Clause 12) types are layout-compatible classes if their common initial sequence comprises all members and bit-fields of both classes (6.9).

And the definition of common initial sequence is in 12.2.20:

The common initial sequence of two standard-layout struct (Clause 12) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types and either neither entity is a bit-field or both are bit-fields with the same width. [Example:
struct A { int a; char b; };
struct B { const int b1; volatile char b2; };
struct C { int c; unsigned : 0; char b; };
struct D { int d; char b : 4; };
struct E { unsigned int e; char b; };
The common initial sequence of A and B comprises all members of either class. The common initial sequence of A and C and of A and D comprises the first member in each case. The common initial sequence of A and E is empty.
— end example]

So from this we can make the following important observations:

  1. Layout compatibility is limited strictly to standard layout classes. (Or enums use the same underlying type or the trivial case when T and T2 are literally the exact same type. See 6.9.11.) In the general case, T is not a standard layout class. In fact, T is not even a class in your example (it is a uint16_t, believe it or not, this matters according to the standard.)*
  2. Even if T is guaranteed to be a standard layout class, struct { T m; } does not have a common initial sequence with T. The sequence of struct { T m; } begins with T, whereas the sequence of T begins with whatever T's non-static data members are. This is actually strictly guaranteed not to be a T as a class cannot contain itself by value.

Therefore, the guarantee cannot be held by the letter of the standard. You should continue to perform the static_assertions to ensure your compiler is behaving in the fashion you expect.

* See most questions on union type punning.

Hurrah answered 10/10, 2017 at 16:12 Comment(5)
Your name is... familiar. Aren't you a game developer? Starbound? Either way, I was looking for an answer that brought the layout into the game; a final "no" to the original question, that states that the static assertions are necessary (and maybe not even enough, since the types are not layout compatible)Shoddy
Yes, bravo Zeta for giving OmnipotentEntity the bounty, +1.Turrell
Zeta, I am. I'm touched you recognized me :) That being said, all is not lost, because even though it's not guaranteed by the standard, it might be guaranteed by your compiler as most of the stuff revolving around alignment is implementation defined.Hurrah
@Hurrah can you have a look at this follow-up question?Shoddy
@Zeta, you're correct that this is undefined behavior, and for the right reasons.Hurrah

© 2022 - 2024 — McMap. All rights reserved.