テンプレートを使用して、自明でない型の共用体の構築を簡素化したいと考えています。以下は実際には「機能する」ように見えますが、仕様では技術的には合法ではありません。
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 によると) 両方の構造が標準レイアウト タイプである場合にのみ、共用体 (つまり、フィールド) 内の 2 つの構造の共通の初期シーケンスにアクセスできることです。destructor_
少なくとも 9.5 の非規範的な注意に従ってください。 .1. 9.0.7 によると、標準レイアウト型は非標準レイアウトの非静的データ メンバーを持つことはできません。したがって、A または B のいずれかが標準レイアウトでない場合destructor_
、間違った共用体でアクセスすることは違法になります。
抜け穴は、に変換してunion_entry
標準レイアウトを作成value_
することのようですalignas(T) char[sizeof(T)]
。9.0.7 では、alignas
. したがって、私の質問: 次はどのタイプの標準レイアウト タイプT
ですか? したがって、前の例をエミュレートするためvalue_
にキャストできますが、アクティブでない ?T&
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 &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_
}