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


16

で遊ん[[no_unique_address]]でいc++20ます。

cppreferenceの例では、空のタイプEmptyとタイプがありますZ

struct Empty {}; // empty class

struct Z {
    char c;
    [[no_unique_address]] Empty e1, e2;
};

どうやら、の大きさはZ、少なくともなければならない2ので、タイプのe1e2同じです。

しかし、私は本当にZサイズが欲しいです1。これ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。サイズをZ11 にするためのコツはありますか?

私はこれをテストしています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同じタイプです。


2
Tそれ自体にテンプレート引数を追加するのはどうですか?それは異なるタイプを生成します。現在、両方Wrapperのsが継承するという事実がTあなたを後退させています...
Max Langhof '29

@MaxLanghofテンプレート引数をに追加するとはどういう意味Tですか?現在、Tテンプレート引数です。
トム

から継承しないでくださいT
平均

@Evgはここでは違いはありません。
eerorika

2
それが1よりも大きいからといって、それが空でないことはありません。coliru.stacked-crooked.com/a/51aa2be4aff4842e
デュプリケータ

回答:


6

どちらの場合は空のタイプであるTS、空の型と異なっています!私はこのタイプが空でもできるようにする場合TS同じタイプです。

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

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

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

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

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

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

また、一意のIDルールにより、それらは異なるアドレスを持つ必要があります。だから、たとえe1e2、名目上の異なる種類あり、その内部にも同じ含むオブジェクト内の他のサブオブジェクトに対して一意の識別に従わなければなりません。再帰的に。

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


素晴らしい説明、ありがとうございました!
トム

2

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

これは便利ですが、残念ながら、完全に空の構造体はsizeof(Empty<Empty<A,A>,A>{})==2どこにAありますか。
トム

get_empty<T>関数を追加します。次に、get_empty<T>onがすでに機能している場合は、onまたはleft を再利用できます。
Yakk-Adam Nevraumont
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.