web-dev-qa-db-ja.com

[[no_unique_address]]と同じタイプの2つのメンバー値

_[[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;
};
_

TSも空のタイプであり、異なる場合、どちらが空のタイプですか。 TSが同じ型であっても、この型を空にしたい。

16
tom

TSも空のタイプであり、異なる場合、どちらが空のタイプですか。 TSが同じ型であっても、この型を空にしたい。

あなたはそれを得ることができません。技術的に言えば、TSが異なる空の型であっても、空であることを保証することはできません。注意:no_unique_addressは属性です。オブジェクトを非表示にする機能は完全に実装に依存します。標準の観点からは、空のオブジェクトのサイズを強制することはできません。

C++ 20の実装が成熟するにつれて、[[no_unique_address]]は通常、空の基本最適化のルールに従うと想定する必要があります。つまり、同じタイプの2つのオブジェクトがサブオブジェクトでない限り、おそらく非表示になることが期待できます。しかし、現時点ではそれは一種のポットラックです。

TSが同じ型である特定のケースに関しては、それは単に不可能です。 「no_unique_address」という名前の意味にもかかわらず、C++では、同じ型のオブジェクトへの2つのポインターが与えられた場合、それらのポインターが同じオブジェクトを指すか、または異なるアドレスを持つことが必要です。これを「一意のIDルール」と呼びますが、no_unique_addressはそれに影響しません。 From [intro.object]/9

ビットフィールドではないライフタイムが重複する2つのオブジェクトは、一方が他方の中にネストされている場合、または少なくとも1つがサイズがゼロのサブオブジェクトであり、それらのタイプが異なる場合、同じアドレスを持つ可能性があります;それ以外の場合は、アドレスが異なり、ストレージのばらばらのバイトを占有します。

[[no_unique_address]]として宣言された空の型のメンバーはサイズがゼロですが、同じ型を持つとこれは不可能になります。

実際、それについて考えて、ネストを介して空の型を非表示にしようとすると、一意のアイデンティティルールに違反します。 WrapperおよびZ1のケースを検討してください。 z1のインスタンスであるZ1が与えられると、z1.e1z1.e2が異なる型の異なるオブジェクトであることは明らかです。ただし、z1.e1z1.e2内にネストされておらず、その逆も同様です。また、タイプは異なりますが、(Empty&)z1.e1(Empty&)z1.e2notタイプが異なります。しかし、それらは異なるオブジェクトを指しています。

そして、一意のアイデンティティルールによって、それらはmustが異なるアドレスを持っています。したがって、e1e2は名目上は異なる型ですが、それらの内部は、同じ包含オブジェクト内の他のサブオブジェクトに対する一意のIDにも従う必要があります。再帰的に。

あなたが何をしようとしているかに関係なく、あなたが望むものは、現在のところ現状ではC++では単純に不可能です。

6
Nicol Bolas

私の知る限りでは、両方のメンバーが必要な場合は不可能です。ただし、型が同じで空の場合は、メンバーを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; };
};
_
2
eerorika