_[[no_unique_address]]
_で_c++20
_をいじっています。
cppreference の例では、空のタイプEmpty
とタイプZ
があります
_struct Empty {}; // empty class
struct Z {
char c;
[[no_unique_address]] Empty e1, e2;
};
_
_2
_と_e1
_の型が同じであるため、Z
のサイズは少なくとも_e2
_である必要があります。
ただし、サイズ_1
_のZ
が本当に必要です。これにより、Empty
を、_e1
_と_e2
_の異なる型を強制する追加のテンプレートパラメーターでラッパークラスにラップすることについて考えました。
_template <typename T, int i>
struct Wrapper : public T{};
struct Z1 {
char c;
[[no_unique_address]] Wrapper<Empty,1> e1;
[[no_unique_address]] Wrapper<Empty,2> e2;
};
_
残念ながら、sizeof(Z1)==2
。 _Z1
_のサイズを1にするためのコツはありますか?
これを_gcc version 9.2.1
_および_clang version 9.0.0
_でテストしています
私のアプリケーションでは、フォームの空のタイプがたくさんあります
_template <typename T, typename S>
struct Empty{
[[no_unique_address]] T t;
[[no_unique_address]] S s;
};
_
T
とS
も空のタイプであり、異なる場合、どちらが空のタイプですか。 T
とS
が同じ型であっても、この型を空にしたい。
T
とS
も空のタイプであり、異なる場合、どちらが空のタイプですか。T
とS
が同じ型であっても、この型を空にしたい。
あなたはそれを得ることができません。技術的に言えば、T
とS
が異なる空の型であっても、空であることを保証することはできません。注意:no_unique_address
は属性です。オブジェクトを非表示にする機能は完全に実装に依存します。標準の観点からは、空のオブジェクトのサイズを強制することはできません。
C++ 20の実装が成熟するにつれて、[[no_unique_address]]
は通常、空の基本最適化のルールに従うと想定する必要があります。つまり、同じタイプの2つのオブジェクトがサブオブジェクトでない限り、おそらく非表示になることが期待できます。しかし、現時点ではそれは一種のポットラックです。
T
とS
が同じ型である特定のケースに関しては、それは単に不可能です。 「no_unique_address」という名前の意味にもかかわらず、C++では、同じ型のオブジェクトへの2つのポインターが与えられた場合、それらのポインターが同じオブジェクトを指すか、または異なるアドレスを持つことが必要です。これを「一意のIDルール」と呼びますが、no_unique_address
はそれに影響しません。 From [intro.object]/9 :
ビットフィールドではないライフタイムが重複する2つのオブジェクトは、一方が他方の中にネストされている場合、または少なくとも1つがサイズがゼロのサブオブジェクトであり、それらのタイプが異なる場合、同じアドレスを持つ可能性があります;それ以外の場合は、アドレスが異なり、ストレージのばらばらのバイトを占有します。
[[no_unique_address]]
として宣言された空の型のメンバーはサイズがゼロですが、同じ型を持つとこれは不可能になります。
実際、それについて考えて、ネストを介して空の型を非表示にしようとすると、一意のアイデンティティルールに違反します。 Wrapper
およびZ1
のケースを検討してください。 z1
のインスタンスであるZ1
が与えられると、z1.e1
とz1.e2
が異なる型の異なるオブジェクトであることは明らかです。ただし、z1.e1
はz1.e2
内にネストされておらず、その逆も同様です。また、タイプは異なりますが、(Empty&)z1.e1
と(Empty&)z1.e2
はnotタイプが異なります。しかし、それらは異なるオブジェクトを指しています。
そして、一意のアイデンティティルールによって、それらはmustが異なるアドレスを持っています。したがって、e1
とe2
は名目上は異なる型ですが、それらの内部は、同じ包含オブジェクト内の他のサブオブジェクトに対する一意のIDにも従う必要があります。再帰的に。
あなたが何をしようとしているかに関係なく、あなたが望むものは、現在のところ現状ではC++では単純に不可能です。
私の知る限りでは、両方のメンバーが必要な場合は不可能です。ただし、型が同じで空の場合は、メンバーを1つだけ特殊化して持つことができます。
_template <typename T, typename S, typename = void>
struct Empty{
[[no_unique_address]] T t;
[[no_unique_address]] S s;
constexpr T& get_t() noexcept { return t; };
constexpr S& get_s() noexcept { return s; };
};
template<typename TS>
struct Empty<TS, TS, typename std::enable_if_t<std::is_empty_v<TS>>>{
[[no_unique_address]] TS ts;
constexpr TS& get_t() noexcept { return ts; };
constexpr TS& get_s() noexcept { return ts; };
};
_
もちろん、メンバーが1人だけの場合に対処するために、メンバーを使用する残りのプログラムを変更する必要があります。この場合、どのメンバーが使用されるかは問題ではありません-結局のところ、一意のアドレスを持たないステートレスオブジェクトです。示されているメンバー関数は、それを単純にするはずです。
残念ながら
sizeof(Empty<Empty<A,A>,A>{})==2
ここで、Aは完全に空の構造体です。
空のペアの再帰的な圧縮をサポートするために、さらに特殊化を導入できます。
_template<class TS>
struct Empty<Empty<TS, TS>, TS, typename std::enable_if_t<std::is_empty_v<TS>>>{
[[no_unique_address]] Empty<TS, TS> ts;
constexpr Empty<TS, TS>& get_t() noexcept { return ts; };
constexpr TS& get_s() noexcept { return ts.get_s(); };
};
template<class TS>
struct Empty<TS, Empty<TS, TS>, typename std::enable_if_t<std::is_empty_v<TS>>>{
[[no_unique_address]] Empty<TS, TS> ts;
constexpr TS& get_t() noexcept { return ts.get_t(); };
constexpr Empty<TS, TS>& get_s() noexcept { return ts; };
};
_
さらに、_Empty<Empty<A, char>, A>
_のようなものを圧縮します。
_template <typename T, typename S>
struct Empty<Empty<T, S>, S, typename std::enable_if_t<std::is_empty_v<S>>>{
[[no_unique_address]] Empty<T, S> ts;
constexpr Empty<T, S>& get_t() noexcept { return ts; };
constexpr S& get_s() noexcept { return ts.get_s(); };
};
template <typename T, typename S>
struct Empty<Empty<S, T>, S, typename std::enable_if_t<std::is_empty_v<S>>>{
[[no_unique_address]] Empty<S, T> st;
constexpr Empty<S, T>& get_t() noexcept { return st; };
constexpr S& get_s() noexcept { return st.get_t(); };
};
template <typename T, typename S>
struct Empty<T, Empty<T, S>, typename std::enable_if_t<std::is_empty_v<T>>>{
[[no_unique_address]] Empty<T, S> ts;
constexpr T& get_t() noexcept { return ts.get_t(); };
constexpr Empty<T, S> get_s() noexcept { return ts; };
};
template <typename T, typename S>
struct Empty<T, Empty<S, T>, typename std::enable_if_t<std::is_empty_v<T>>>{
[[no_unique_address]] Empty<S, T> st;
constexpr T& get_t() noexcept { return st.get_s(); };
constexpr Empty<S, T> get_s() noexcept { return st; };
};
_