Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 554
Zeta Avatar asked Sep 26 '17 11:09

Zeta


Video Answer


2 Answers

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.

like image 75
gsamaras Avatar answered Oct 07 '22 23:10

gsamaras


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 :).

like image 7
Tristan Brindle Avatar answered Oct 08 '22 00:10

Tristan Brindle