[[no_unique_address]] e due valori membro dello stesso tipo


16

Sto giocando con [[no_unique_address]]dentro c++20.

Nell'esempio su cppreference abbiamo un tipo Emptye un tipo vuotiZ

struct Empty {}; // empty class

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

Apparentemente, la dimensione di Zdeve essere almeno 2perché i tipi di e1e e2sono gli stessi.

Tuttavia, voglio davvero avere Zcon le dimensioni 1. Questo mi ha fatto pensare, che dire del wrapping Emptyin qualche classe wrapper con un parametro template aggiuntivo che impone diversi tipi di e1e 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;
};

Purtroppo, sizeof(Z1)==2. C'è un trucco per farne una dimensione Z1?

Sto testando questo con gcc version 9.2.1eclang version 9.0.0


Nella mia domanda, ho molti tipi vuoti del modulo

template <typename T, typename S>
struct Empty{
    [[no_unique_address]] T t;
    [[no_unique_address]] S s;
};

Che è un tipo vuoto se Te Ssono anche tipi vuoti e distinti! Voglio che questo tipo sia vuoto anche se Te Ssono gli stessi tipi.


2
Che dire di aggiungere argomenti modello a Tse stesso? Ciò genererebbe tipi distinti. In questo momento il fatto che entrambi Wrapperereditino Tti sta trattenendo ...
Max Langhof,

@MaxLanghof Cosa intendi con l'aggiunta di un argomento template T? In questo momento, Tè un argomento modello.
tom

Non ereditare da T.
Evg

@Evg non fa differenza qui.
Eerorika,

2
Solo perché è più grande di 1 non lo rende non vuoto: coliru.stacked-crooked.com/a/51aa2be4aff4842e
Deduplicatore

Risposte:


6

Che è un tipo vuoto se Te Ssono anche tipi vuoti e distinti! Voglio che questo tipo sia vuoto anche se Te Ssono gli stessi tipi.

Non puoi capirlo. Tecnicamente parlando, non puoi nemmeno garantire che sarà vuoto anche se Te Ssono diversi tipi vuoti. Ricorda: no_unique_addressè un attributo; la capacità di nascondere gli oggetti dipende interamente dall'implementazione. Dal punto di vista standard, non è possibile applicare la dimensione degli oggetti vuoti.

Man mano che le implementazioni di C ++ 20 maturano, dovresti presumere che [[no_unique_address]]generalmente seguiranno le regole dell'ottimizzazione della base vuota. Vale a dire, fintanto che due oggetti dello stesso tipo non sono oggetti secondari, probabilmente ci si può aspettare di nascondersi. Ma a questo punto, è una specie di fortuna.

Quanto al caso specifico di Ted Sessere dello stesso tipo, semplicemente non è possibile. Nonostante le implicazioni del nome "no_unique_address", la realtà è che C ++ richiede che, dati due puntatori a oggetti dello stesso tipo, questi puntatori puntino allo stesso oggetto o abbiano indirizzi diversi. La chiamo "regola dell'identità univoca" e no_unique_addressnon influisce su questo. Da [intro.object] / 9 :

Due oggetti con durata di vita sovrapposta che non sono campi di bit possono avere lo stesso indirizzo se uno è nidificato all'interno dell'altro o se almeno uno è un oggetto secondario di dimensione zero e sono di tipo diverso ; in caso contrario, hanno indirizzi distinti e occupano byte di memoria disgiunti.

I membri di tipi vuoti dichiarati come [[no_unique_address]]di dimensione zero, ma avere lo stesso tipo lo rendono impossibile.

In effetti, pensandoci, il tentativo di nascondere il tipo vuoto tramite annidamento viola ancora la regola di identità univoca. Considera il tuo Wrappere il tuo Z1caso. Dato z1che è un'istanza di Z1, è chiaro che z1.e1e z1.e2sono oggetti diversi con tipi diversi. Tuttavia, z1.e1non è nidificato all'interno z1.e2né viceversa. E mentre hanno tipi diversi (Empty&)z1.e1e non(Empty&)z1.e2 sono tipi diversi. Ma indicano oggetti diversi.

E secondo la regola dell'identità univoca, devono avere indirizzi diversi. Quindi, anche se e1e e2sono tipi nominalmente diversi, i loro interni devono anche obbedire all'identità univoca contro altri oggetti secondari nello stesso oggetto contenitore. Ricorsivamente.

Quello che vuoi è semplicemente impossibile in C ++ così com'è attualmente, indipendentemente da come ci provi.


Grande spiegazione, grazie mille!
tom

2

Per quanto ne so, ciò non è possibile se si desidera avere entrambi i membri. Ma puoi specializzarti e avere solo uno dei membri quando il tipo è uguale e vuoto:

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

Ovviamente, il resto del programma che utilizza i membri dovrebbe essere modificato per affrontare il caso in cui vi è un solo membro. Non dovrebbe importare quale membro viene utilizzato in questo caso - dopo tutto, è un oggetto senza stato senza indirizzo univoco. Le funzioni membro mostrate dovrebbero renderlo semplice.

purtroppo sizeof(Empty<Empty<A,A>,A>{})==2dove A è una struttura completamente vuota.

Potresti introdurre più specializzazioni per supportare la compressione ricorsiva di coppie vuote:

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

Ancora di più, per comprimere qualcosa del genere 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; };
};

Questo è bello, ma ancora purtroppo sizeof(Empty<Empty<A,A>,A>{})==2in cui Aè una struttura completamente vuoto.
tom

Aggiungerei una get_empty<T>funzione. Quindi è possibile riutilizzare get_empty<T>a sinistra oa destra se funziona già lì.
Yakk - Adam Nevraumont,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.