2

Under what circumstances does the standard require that T has exactly the same size and alignment as std::aligned_storage<sizeof(T), alignof(T)> ?

I thought the answer was, always, but from other SO answers today, I learned that for some obscure T, it may be implementation defined. If the implementation supports extended alignment types, like 128 bit integers, it is up to the implementation whether std::aligned_storage will end up having that 128 bit alignment, if I understood correctly.

Are there other types T where the answer to the question may be implementation-defined?


In my application, I basically have a tuple std::tuple<T, U, V> of unknown types, and I want to be able to get the offsets of the members T, U, V within this tuple as efficiently as possible, ideally at compile-time.

(I realize that this is a very strange thing to want. I'm trying to fix up some existing code where a tuple is being overlaid on top of a "layout-compatible" type using reinterpret_cast. That kind of cast between unrelated objects is illegal and violates strict aliasing rules. If I can get the offsets of the members instead, then I can factor out the illegal cast.)

AFAIK, this can't quite be done within C++14 constexpr. But it can come pretty close, my code looks like this:

#include <type_traits>
#include <tuple>
#include <utility>
#include <memory>

template <typename T>
struct tuple_of_aligned_storage;

template <typename... Ts>
struct tuple_of_aligned_storage<std::tuple<Ts...>> {
  using type = std::tuple<std::aligned_storage_t<sizeof(Ts), alignof(Ts)>...>;
};

template <typename T>
using tuple_of_aligned_storage_t = typename tuple_of_aligned_storage<T>::type;

template <typename S>
struct offset_helper {

  // Get offset of idx'th member
  template <std::size_t idx>
  static std::ptrdiff_t offset() {
    constexpr tuple_of_aligned_storage_t<S> layout{};
    // TODO: Do modern compilers optimize `layout` object out of the binary?
    // If so, this call is probably inlined.
    return reinterpret_cast<const char *>(&std::get<idx>(layout)) - reinterpret_cast<const char *>(&layout);
  }
};

int main() {
  using T = std::tuple<int, float, double>;
  return offset_helper<T>::offset<0>(); // offset of 0'th member
}

In experiments, most modern compilers optimize this through to a constant, even though there are reinterpret casts involved and such, and the offset function can't quite be marked constexpr.

The point of using std::aligned_storage here is, even if the members of the tuple are not default constructible, have nontrivial initialization, etc. replacing them with std::aligned_storage makes them trivially constructible without changing the layout of the tuple.

But, if for some types, swapping the type for std::aligned_storage changes the size or alignment, that can break my whole approach and lead to the wrong offsets being calculated.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • 1
    I don't think any actual requirements are made by the standard; the size of the aligned storage could be 1000 times the size of `T`. It'd be stupid, but I don't think it's forbidden. The intention is that it's effectively an `alignas(T) char[sizeof(T)]` (and `alignas` has constraints regarding extended alignment). – Kerrek SB Sep 28 '17 at 00:43
  • Thanks, I guess that means I really should write that implementation myself and use it. Standard seems to require only that it produces storage with at least enough size and alignment, but maybe more – Chris Beck Sep 28 '17 at 00:55
  • 2
    I would probably complain to my vendor if they implemented something more stupidly than I could do myself :-) – Kerrek SB Sep 28 '17 at 01:12

0 Answers0