2009-03-23 20 views
12

supponiamo Ho uno struct come questo:È possibile utilizzare i modelli per accedere alle variabili struct in base al nome?

struct my_struct 
{ 
    int a; 
    int b; 
} 

Ho una funzione che dovrebbe impostare un nuovo valore per entrambi "a" o "b". Questa funzione richiede anche di specificare quale variabile impostare. Un tipico esempio sarebbe questo:

void f(int which, my_struct* s, int new_value) 
{ 
    if(which == 0) 
    s->a = new_value; 
    else 
    s->b = new_value; 
} 

Per ragioni che non scriverò qui non posso passare il puntatore a/b per f. Quindi non posso chiamare f con indirizzo my_struct :: a o my_struct :: b. Un'altra cosa che non posso fare è dichiarare un vettore (int vars [2]) all'interno di my_struct e passare un intero come indice a f. Fondamentalmente in f ho bisogno di accedere alle variabili per nome.

Il problema con l'esempio precedente è che in futuro prevedo di aggiungere più variabili a struct e in tal caso ricorderò di aggiungere ulteriori istruzioni if ​​a f, il che è negativo per la portabilità. Una cosa che ho potuto fare è scrivere f come una macro, come questo:

#define FUNC(which) 
void f(my_struct* s, int new_value) \ 
{ \ 
     s->which = new_value; \ 
} 

e poi ho potuto chiamare FUNC (a) o FUNC (b).

Questo funzionerebbe ma non mi piace usare i macro. Quindi la mia domanda è: esiste un modo per raggiungere lo stesso obiettivo utilizzando i modelli anziché le macro?

EDIT: proverò a spiegare perché non posso usare i puntatori e ho bisogno di accedere alla variabile per nome. Fondamentalmente la struttura contiene lo stato di un sistema. Questo sistema deve "annullare" il suo stato quando richiesto. Annulla è gestito tramite un'interfaccia chiamata undo_token come questo:

class undo_token 
{ 
public: 
    void undo(my_struct* s) = 0; 
}; 

Quindi non posso passare puntatori al metodo di annullamento a causa del polimorfismo (MyStruct contiene variabili di altri tipi pure).

Quando aggiungo una nuova variabile per la struttura Io in genere anche aggiungere una nuova classe, in questo modo:

class undo_a : public undo_token 
{ 
    int new_value; 
public: 
    undo_a(int new_value) { this->new_value = new_value; } 
    void undo(my_struct *s) { s->a = new_value} 
}; 

problema è che non so puntatore s quando creo il token, quindi non posso salva un puntatore a s :: a nel costruttore (che avrebbe risolto il problema). La classe per "b" è la stessa, devo solo scrivere "s-> b" invece di s-> a

Forse questo è un problema di progettazione: ho bisogno di un token di annullamento per tipo di variabile, non uno per variabile ...

+0

FUNC (a) - definirà f (s, nuovo_valore). Ma come lo chiamerai allora? –

+0

Sì, mi dispiace che sia stato un errore. Ma spero che tu abbia capito il punto :) – Emiliano

+0

Anche dopo la tua modifica, usando la mia risposta e Mykola Golubyev, puoi ancora parametrizzare la tua classe di annullamento con un puntatore a un membro di dati di classe e legarla alla classe/struct al richiamo del membro tempo. – camh

risposta

16
#include <iostream> 
#include <ostream> 
#include <string> 

struct my_struct 
{ 
    int a; 
    std::string b; 
}; 

template <typename TObject, typename TMember, typename TValue> 
void set(TObject* object, TMember member, TValue value) 
{ 
    (*object).*member = value; 
} 

class undo_token {}; 

template <class TValue> 
class undo_member : public undo_token 
{ 
    TValue new_value_; 
    typedef TValue my_struct::* TMember; 
    TMember member_; 

public: 
    undo_member(TMember member, TValue new_value): 
     new_value_(new_value), 
     member_(member) 
    {} 

    void undo(my_struct *s) 
    { 
     set(s, member_, new_value_); 
    } 
};  

int main() 
{ 
    my_struct s; 

    set(&s, &my_struct::a, 2); 
    set(&s, &my_struct::b, "hello"); 

    std::cout << "s.a = " << s.a << std::endl; 
    std::cout << "s.b = " << s.b << std::endl; 

    undo_member<int> um1(&my_struct::a, 4); 
    um1.undo(&s); 

    std::cout << "s.a = " << s.a << std::endl; 

    undo_member<std::string> um2(&my_struct::b, "goodbye"); 
    um2.undo(&s); 

    std::cout << "s.b = " << s.b << std::endl; 

    return 0; 
} 
+0

Hai letto il pezzo in cui diceva che non poteva prendere l'indirizzo di aeb? –

+0

Non significa che non può passare cose come & s.a a una funzione. –

+0

Perfetto! Questo ha risolto il mio problema. Intelligente E semplice.Grazie – Emiliano

2

Non è possibile utilizzare i modelli per risolvere questo problema, ma perché utilizzare una struttura al primo posto? Questo sembra un uso ideale per una std :: map che dovrebbe mappare i nomi ai valori.

+1

perché "f" è solo una funzione necessaria per fare cose secondarie sulla struttura. Non voglio che la forma della struttura sia vincolata a ciò che "f" ha bisogno. – Emiliano

4

Non sono sicuro del motivo per cui non è possibile utilizzare un puntatore quindi non so se questo è appropriato, ma dare un'occhiata a C++: Pointer to class data member, che descrive un modo in cui è possibile passare un puntatore a un membro dati di una struttura/classe che non punta direttamente al membro, , ma viene successivamente associata a un puntatore struct/class. (enfasi aggiunta dopo la modifica del poster che spiega perché un puntatore non può essere usato)

In questo modo non si passa un puntatore al membro, ma è più come un offset all'interno di un oggetto.

3

Suona come quello che stai cercando si chiama "reflection", e sì è spesso realizzato con una combinazione di modelli e macro. Tieni presente che le soluzioni di riflessione sono spesso complicate e fastidiose con cui lavorare, quindi potresti voler fare qualche ricerca prima di immergerti nel codice per scoprire se questo è davvero quello che vuoi.

Il secondo successo su Google per "modelli di riflessione C++" era un documento su "Reflection support by means of template metaprogramming". Questo dovrebbe iniziare. Anche se non è proprio quello che stai cercando, potrebbe mostrarti un modo per risolvere il tuo problema.

2

Da quello che hai descritto, suppongo che tu non abbia modo di ridefinire la struttura.

Se l'hai fatto, ti suggerirei di usare Boost.Fusion per descrivere la tua struttura con campi con nome modello. Vedi associative tuples per maggiori informazioni. Entrambi i tipi di strutture potrebbero essere effettivamente compatibili (stessa organizzazione in memoria), ma sono abbastanza sicuro che non ci sia modo di ottenere tale garanzia dallo standard.

In caso contrario, è possibile creare un complemento alla struttura che consente di accedere ai campi nello stesso modo in cui le tuple associative fanno. Ma può essere un po 'verbale.

EDIT

Ora è abbastanza chiaro che è possibile definire le strutture il modo in cui si desidera. Quindi ti consiglio di usare boost.fusion.

34

Per rispondere alla domanda esatta, c'è, ma è piuttosto complicato, e sarà puramente una cosa in fase di compilazione. (Se hai bisogno di una ricerca runtime, usa un pointer-to-member e sulla tua domanda aggiornata, potresti aver frainteso il loro funzionamento.)

In primo luogo, hai bisogno di qualcosa che puoi usare per rappresentare il "nome di un membro "al momento della compilazione. Nella metaprogrammazione in fase di compilazione, tutto a parte gli interi deve essere rappresentato dai tipi. Quindi utilizzerai un tipo per rappresentare un membro.

Ad esempio, un membro del tipo integer che memorizza l'età di una persona, e un altro per memorizzare il loro cognome:

struct age { typedef int value_type; }; 
struct last_name { typedef std::string value_type; }; 

allora avete bisogno di qualcosa come un map che fa ricerca in fase di compilazione. Chiamiamolo ctmap. Diamo supporto per un massimo di 8 membri. In primo luogo abbiamo bisogno di un segnaposto per rappresentare l'assenza di un campo:

struct none { struct value_type {}; }; 

Poi possiamo inoltrare-dichiarare la forma di ctmap:

template < 
    class T0 = none, class T1 = none, 
    class T2 = none, class T3 = none, 
    class T4 = none, class T5 = none, 
    class T6 = none, class T7 = none 
    > 
struct ctmap; 

Abbiamo poi specializzati questo per il caso in cui non ci sono campi :

template <> 
struct ctmap< 
    none, none, none, none, 
    none, none, none, none 
    > 
{ 
    void operator[](const int &) {}; 
}; 

Il motivo per questo sarà chiaro (possibilmente) in un momento. Infine, la definizione per tutti gli altri casi:

template < 
    class T0, class T1, class T2, class T3, 
    class T4, class T5, class T6, class T7 
    > 
    struct ctmap : public ctmap<T1, T2, T3, T4, T5, T6, T7, none> 
    { 
     typedef ctmap<T1, T2, T3, T4, T5, T6, T7, none> base_type; 

     using base_type::operator[]; 
     typename T0::value_type storage; 

     typename T0::value_type &operator[](const T0 &c) 
     { return storage; } 
}; 

Che diavolo sta succedendo qui?Se mettete:

ctmap<last_name, age> person; 

C++ costruirà un tipo per person ricorsivamente espandendo i modelli, perché ctmaperedita da sé, fornendo di archiviazione per il primo campo e poi disfarsene quando abbiamo ereditato. Tutto questo viene ad un arresto improvviso quando non ci sono più campi, perché la specializzazione per all- none calci in

Quindi possiamo dire:.

person[last_name()] = "Smith"; 
person[age()] = 104; 

E 'come guardare in un map, ma a compilare il tempo, utilizzando una classe di denominazione dei campi come chiave.

Questo significa che possiamo anche fare questo:

template <class TMember> 
void print_member(ctmap<last_name, age> &person) 
{ 
    std::cout << person[TMember()] << std::endl; 
} 

Questa è una funzione che stampa il valore di uno dei membri, in cui il membro da stampare è un parametro di tipo. Così possiamo chiamare in questo modo:

print_member<age>(person); 

Quindi sì, è possibile scrivere una cosa che è un po 'come un struct, un po' come un tempo di compilazione map.

+5

Non esattamente la cosa più pulita del mondo, ma comunque intelligente. +1 solo per insegnarmi qualcosa di nuovo. –

+2

Una risposta molto completa, ma è una specie di esagerazione per ciò di cui ho bisogno. Come ha detto Dan, hai un +1 per insegnarmi qualcosa di nuovo, comunque. – Emiliano

+0

Grazie. Spero che qualcuno ci inciampi e lo trovi utile: il titolo della domanda sembra funzionare in questo modo! –

1

Non riesco a pensare a un motivo per cui non avresti tutto a portata di mano quando crei un comando di annullamento. Quello che vuoi essere in grado di annullare, hai fatto. Quindi credo che si possano usare i puntatori ai membri della classe e persino i puntatori ai campi di una particolare istanza di classe quando si crea il comando annulla.

Hai ragione nella tua sezione MODIFICA. Lo è una questione di design.

+0

Perché ci sono alcuni punti nel codice in cui lo stato del sistema è definito da "punti di controllo". Questo è implmentato cancellando lo stato attuale e creando uno nuovo. È vero che nel 90% dei casi il puntatore è lo stesso, ma questo non è sempre il caso. – Emiliano

+0

Se elimini un oggetto stato e ne crei uno nuovo, perché non trovi un modo per non eliminare lo stato precedente, ma per disabilitarlo in modo che, se annulli l'eliminazione, stai ancora lavorando con lo stesso oggetto? Potrei non essere chiaro, mi dispiace, ma non sono sicuro di aver capito bene! –

+0

Beh suppongo che se posso affermare che il puntatore di stato del sistema è sempre valido di quanto non ci sia alcun punto nella mia domanda. Ancora, non sono sicuro che modificare il codice esistente in modo che possa adattarsi a "supportarlo" è una buona idea. Vedi anche il mio commento alla risposta di Neil Butterworth. – Emiliano

6

Mykola Golubyev's answer è buona, ma può essere migliorata leggermente sfruttando il fatto che i puntatori ai membri possono essere usate come non-tipo parametri del modello:

#include <iostream> 
#include <ostream> 
#include <string> 

struct my_struct 
{ 
    int a; 
    std::string b; 
}; 

template <typename TObject, typename TMember, typename TValue> 
void set(TObject* object, TMember member, TValue value) 
{ 
    (*object).*member = value; 
} 

class undo_token {}; 

template <class TValue, TValue my_struct::* Member> 
class undo_member : public undo_token 
{ 
     // No longer need to store the pointer-to-member 
     TValue new_value_; 

public: 
     undo_member(TValue new_value): 
       new_value_(new_value) 
     {} 

     void undo(my_struct *s) 
     { 
       set(s, Member, new_value_); 
     } 
};  

int main() 
{ 
    my_struct s; 

    set(&s, &my_struct::a, 2); 
    set(&s, &my_struct::b, "hello"); 

    std::cout << "s.a = " << s.a << std::endl; 
    std::cout << "s.b = " << s.b << std::endl; 

    undo_member<int, &my_struct::a> um1(4); 
    um1.undo(&s); 

    std::cout << "s.a = " << s.a << std::endl; 

    undo_member<std::string, &my_struct::b> um2("goodbye"); 
    um2.undo(&s); 

    std::cout << "s.b = " << s.b << std::endl; 

    return 0; 
} 

Questa rade il costo di un puntatore a membro da ogni istanza di undo_member.

+0

+1. Non ho mai visto questo. Ma questo creerà molte classi una per ogni membro della struttura. –

+0

@Mykola: È vero, ma è un problema? I metodi saranno tutti delineati dal compilatore, quindi non credo che ci sarà alcun codice gonfiato. –

+0

Non saranno in linea, poiché nella domanda originale il metodo di annullamento è virtuale (beh, inteso che sia - il valore = 0 nella classe base lo restituisce, ma la parola chiave virtuale sembra che sia stata accidentalmente interrotta) – camh

7

Oltre allo Daniel Earwicker's answer, è possibile utilizzare modelli variadic nel nuovo standard C++ per ottenere lo stesso risultato.

template <typename T> 
struct Field { 
    typename T::value_type storage; 

    typename T::value_type &operator[](const T &c) { 
    return storage; 
    } 
}; 

template<typename... Fields> 
struct ctmap : public Field<Fields>... { 
}; 

Questo codice è più pulito e non ha un limite fisso di membri. Puoi usarlo allo stesso modo

struct age { typedef int value_type; }; 
struct last_name { typedef std::string value_type; }; 

ctmap<last_name, age> person; 

person[last_name()] = "Smith"; 
person[age()] = 104; 
+2

Puoi andare [un passo avanti] (http://coliru.stacked-crooked.com/a/c5ec6cdf781525a1), dove ci avviciniamo ancora di più al campo. – Yakk

Problemi correlati