È vietato l’uso da puntatore a “struttura interna”?

Ho una struttura nidificata e mi piacerebbe avere un puntatore a membro di uno dei membri nidificati:

è legale?

struct InnerStruct { bool c; }; struct MyStruct { bool t; bool b; InnerStruct inner; }; 

Questo:

 MyStruct mystruct; //... bool MyStruct::* toto = &MyStruct::b; 

è ok ma:

 bool MyStruct::* toto = &MyStruct::inner.c; 

non è. qualche idea?

Grazie

Ecco alcuni dettagli Sì, è & MyStruct :: be non mystruct :: b; Il codice proviene da un sistema RTTI / Property personalizzato. Per ogni class specificata manteniamo un array di “Proprietà”, incluso un Ptr-to-member. Viene usato in questo modo:

 //somewhere else in code... ( myBaseClassWithCustomRTTIPointer)->* toto = true; 

Sì, è proibito. Non sei il primo a escogitare questa idea perfettamente logica. Secondo me questo è uno degli ovvi “bug” / “omissioni” nelle specifiche dei puntatori ai membri in C ++, ma a quanto pare il comitato non ha alcun interesse a sviluppare ulteriormente le specifiche dei puntatori-membri (come il caso con la maggior parte delle caratteristiche linguistiche “di basso livello”).

Nota che tutto il necessario per implementare la funzione è già lì, nella lingua. Un puntatore a membro-data-di-un-membro non è in alcun modo diverso da un puntatore a un membro dati immediato. L’unica cosa che manca è la syntax per inizializzare tale puntatore. Tuttavia, apparentemente il comitato non è interessato a introdurre una tale syntax.

Dal punto di vista della pura logica formale, questo dovrebbe essere permesso in C ++

 struct Inner { int i; int j[10]; }; struct Outer { int i; int j[10]; Inner inner; }; Outer o; int Outer::*p; p = &Outer::i; // OK o.*p = 0; // sets `oi` to 0 p = &Outer::inner.i; // ERROR, but should have been supported o.*p = 0; // sets `o.inner.i` to 0 p = &Outer::j[0]; // ERROR, but should have been supported o.*p = 0; // sets `oj[0]` to 0 // This could have been used to implement something akin to "array type decay" // for member pointers p = &Outer::j[3]; // ERROR, but should have been supported o.*p = 0; // sets `oj[3]` to 0 p = &Outer::inner.j[5]; // ERROR, but should have been supported o.*p = 0; // sets `o.inner.j[5]` to 0 

Un’implementazione tipica del membro pointer-to-data non è altro che un semplice offset di byte del membro dall’inizio dell’object che lo racchiude. Poiché tutti i membri (immediati e membri dei membri) vengono alla fine disposti in sequenza nella memoria, i membri dei membri possono anche essere identificati da uno specifico valore di offset. Questo è ciò che intendo quando dico che il funzionamento interno di questa funzione è già pienamente implementato, tutto ciò che è necessario è la syntax di inizializzazione.

Nel linguaggio C questa funzionalità viene emulata da offset espliciti ottenuti tramite lo standard offsetof macro. E in CI puoi ottenere offsetof(Outer, inner.i) e offsetof(Outer, j[2]) . Sfortunatamente, questa capacità non si riflette nei membri dei puntatori-dati-C ++.

L’InnerStruct che ti interessa è contenuto in un’istanza di MyStruct, ma ciò non influisce sul modo in cui ottieni un puntatore al membro di InnerStruct.

 bool InnerStruct::* toto2 = &InnerStruct::c; 

Modifica: rileggendo la tua domanda, suppongo che tu voglia definire un puntatore al membro della struttura esterna e farlo puntare direttamente su un membro della struttura interna. Questo semplicemente non è permesso. Per arrivare al membro della struttura interna che è contenuta nella struttura esterna, dovresti creare un puntatore alla struttura interna itselft, quindi al suo membro. Per usarlo, dovresti dereferenziare entrambi i puntatori ai membri:

 // Pointer to inner member of MyStruct: InnerStruct MyStruct::* toto = &MyStruct::inner; // Pointer to c member of InnerStruct: bool InnerStruct::* toto2 = &InnerStruct::c; // Dereference both to get to the actual bool: bool x = mystruct.*toto.*toto2; 

Come affermato nella risposta di AnT, questa sembra in effetti un’omissione dello standard e la mancanza di una syntax corretta per esprimere ciò che si desidera fare. Un mio collega si è scontrato con questo problema l’altro giorno e ha citato la tua domanda e la sua risposta come prova che non si poteva fare. Beh, mi piace una sfida, e sì, si può fare … ma non è carina.

Innanzitutto, è importante rendersi conto che un puntatore al membro è fondamentalmente un offset da un puntatore a struct [1] . Il linguaggio ha un offset di operatore che è sospettosamente simile a questo, e abbastanza interessante ci dà l’espressività richiesta per fare ciò che vogliamo.

Il problema che colpiamo immediatamente è che C ++ proibisce di lanciare i puntatori ai membri. Bene, quasi … abbiamo il sindacato impegnato a prepararci per questo. Come ho detto, non è carino!

Infine, dobbiamo anche conoscere il tipo di puntatore corretto su cui eseguire il cast.

Quindi, senza ulteriori indugi, ecco il codice (testato su gcc e clang):

 template  union MemberPointerImpl final { template  struct Helper { using Type = UC::*; }; template  struct Helper { using Type = UC::*; }; using MemberPointer = typename Helper::Type; MemberPointer o; size_t i = P; // we can't do "auto i" - argh! static_assert(sizeof(i) == sizeof(o)); }; #define MEMBER_POINTER(C, M) \ ((MemberPointerImpl<__typeof__(C), \ decltype(((__typeof__(C)*)nullptr)->M), \ __builtin_offsetof(__typeof__(C), M) \ >{ }).o) 

Diamo prima un’occhiata alla macro MEMBER_POINTER . Ci vogliono due argomenti. Il primo, C , è la struct che sarà la base per il puntatore del membro. Il wrapping in __typeof__ non è strettamente necessario, ma consente di passare un tipo o una variabile. Il secondo argomento, M , fornisce un’espressione al membro di cui vogliamo il puntatore.

La macro MEMBER_POINTER estrae due ulteriori informazioni da questi argomenti e le passa come parametri MemberPointerImpl modello MemberPointerImpl . Il primo pezzo è il tipo di membro indicato. Questo viene fatto costruendo un’espressione usando un puntatore nullo su cui usiamo decltype . Il secondo pezzo è l’offset dalla struttura di base al membro in questione.

All’interno di MemberPointerImpl è necessario build il tipo MemberPointer che sarà ciò che viene restituito dalla macro. Questo viene fatto con una struttura helper che rimuove i riferimenti che si verificano inutilmente se il nostro membro è un elemento dell’array, consentendo il supporto anche per questo. Permette anche a gcc e clang di darci un bel tipo completamente espanso nella diagnostica se assegniamo il valore restituito a una variabile con un tipo non corrispondente.

Pertanto, per utilizzare MEMBER_POINTER , è sufficiente modificare il codice da:

 bool MyStruct::* toto = &MyStruct::inner.c; 

a:

 bool MyStruct::* toto = MEMBER_POINTER(MyStruct, inner.c); 

[1] Ok, attimi di caucanvas: questo potrebbe non essere vero per tutte le architetture / compilatori, quindi gli scrittori di codici portatili ora guardano altrove!