Могут ли типы стандартной компоновки C 14 использовать `выравнивания` для полей?

#c #c 14 #standard-layout

#c #c 14 #стандартная компоновка

Вопрос:

Я хотел бы использовать шаблоны для упрощения построения объединений с нетривиальными типами. Следующее, похоже, «работает» на практике, но технически не является законным по спецификации:

 template<typename T> struct union_entry {
  void (*destructor_)(void *);  // how to destroy type T when active
  T value_;
};
union U {
  union_entry<A> a;
  union_entry<B> b;
  // ... some constructor and destructor...
};
  

Проблема в том, что (согласно N4141) вы можете получить доступ к общей начальной последовательности двух структур в объединении (т. Е. К destructor_ полю), только если обе структуры являются стандартными типами компоновки — по крайней мере, согласно ненормативному примечанию в 9.5.1. Согласно 9.0.7, стандартный тип компоновки можету меня нет нестатических элементов данных с нестандартной компоновкой. Итак, если A или B не являются стандартной компоновкой, то доступ destructor_ к неправильному объединению становится незаконным.

Казалось бы, лазейка заключается в создании union_entry стандартной компоновки путем перехода value_ к alignas(T) char[sizeof(T)] . Похоже, ничто в версии 9.0.7 не исключает использование alignas . Таким образом, мой вопрос: является ли следующий тип стандартной компоновки для любого типа T ?И, следовательно, может value_ быть применен Tamp; для эмуляции предыдущего примера, в то же время позволяя destructor_ использовать в неактивном union_entry ?

 template<typename T> struct union_entry {
  void (*destructor_)(void *);
  alignas(T) char value_[sizeof(T)];
}
  

Как в clang-3.8.1, так и в g -6.2.1 std::is_standard_layout предполагается union_entry<T> , что это стандартная компоновка, даже если T это не так. Вот полный рабочий пример того, как я хотел бы использовать эту технику:

 #include <cassert>
#include <iostream>
#include <new>
#include <string>

using namespace std;

template<typename T> struct union_entry {
  void (*destructor_)(void *);
  alignas(T) char value_[sizeof(T)];

  union_entry() : destructor_(nullptr) {}
  ~union_entry() {}   // Just to cause error in unions w/o destructors

  void select() {
    if (destructor_)
      destructor_(this);
    destructor_ = destroy_helper;
    new (static_cast<void *>(value_)) T{};
  }
  T amp;get() {
    assert(destructor_ == destroy_helper);
    return *reinterpret_cast<T *>(value_);
  }

private:
  static void destroy_helper(void *_p) {
    union_entry *p = static_cast<union_entry *>(_p);
    p->get().~T();
    p->destructor_ = nullptr;
  }
};

union U {
  union_entry<int> i;
  union_entry<string> s;
  U() : i() {}
  ~U() { if (i.destructor_) i.destructor_(this); }
};

int
main()
{
  U u;
  u.i.select();
  u.i.get() = 5;
  cout << u.i.get() << endl;
  u.s.select();
  u.s.get() = "hello";
  cout << u.s.get() << endl;
  // Notice that the string in u.s is destroyed by calling
  // u.i.destructor_, not u.s.destructor_
}
  

Комментарии:

1. возможно, вы захотите взглянуть на std::aligned_storage<> , но также вы можете захотеть взглянуть на boost::variant<> . Может быть, вы могли бы просто поместить указатель деструктора за пределы объединения, поскольку он вам понадобится для каждого члена, предположительно.

Ответ №1:

Благодаря @Arvid, который указал мне std::aligned_storage , я считаю, что в разделе 20.10.7.6 стандарта есть окончательный (хотя и ненормативный) ответ (который, я полагаю, совпадает с N4141).

Во-первых, в таблице 57 говорится о aligned_storage том, что «Тип элемента typedef type
должен быть типом POD …», где 9.0.10 разъясняет, что «Структура POD — это класс без объединения, который является одновременно тривиальным классом и классом стандартной компоновки«.

Далее, 20.10.7.6.1 дает ненормативный пример реализации:

 template <std::size_t Len, std::size_t Alignment>
struct aligned_storage {
  typedef struct {
    alignas(Alignment) unsigned char __data[Len];
  } type;
};
  

Очевидно, что использование alignas не препятствует тому, чтобы тип был стандартным. макет.