2009-11-10 15 views
11

Ho appena iniziato a giocare con metaprogrammazione e sto lavorando su diversi compiti solo per esplorare il dominio. Uno di questi era di generare un numero intero univoco e mappa per digitare, come di seguito:Tipo metaprogrammo per convertire il tipo in numero univoco

int myInt = TypeInt<AClass>::value; 

Dove valore dovrebbe essere un momento della compilazione costante, che a sua volta può essere utilizzato ulteriormente nei programmi di meta.

Voglio sapere se questo è possibile, e in tal caso come. Perché anche se ho imparato molto sull'esplorare questo argomento, non ho ancora trovato una risposta.

(PS A sì/no risposta è molto più gratificante di una soluzione C++ che non utilizza metaprogrammazione, in quanto questo è il dominio che sto esplorando)

+0

Si desidera anche supportare tipi come 'void (*) (AClass const (*) [4])'? Penso che sia ancora possibile trattare un tipo come un albero in cui i tipi fondamentali e i tipi definiti dall'utente sono le foglie. – sellibitze

+0

L'esempio non richiede che ':: value' sia una costante di tempo di compilazione. È un requisito? –

+1

Penso che tecnicamente, questo è impossibile da realizzare, anche se per tipi arbitrari: ci sono molti più tipi di quanti ce ne siano possibili. Ricorda ogni tipo di 'struct A; struct B; B [1]; B [2]; 'etc sono diversi. Come potrebbe mai funzionare? –

risposta

3

Il più vicino Sono venuto fin qui è essere in grado di mantenere un elenco dei tipi mentre il monitoraggio della distanza di nuovo alla base (dando un valore unico). Notare la "posizione" qui sarà unico al vostro tipo se si traccia le cose correttamente (vedi il principale per l'esempio)

template <class Prev, class This> 
class TypeList 
{ 
public: 
    enum 
    { 
     position = (Prev::position) + 1, 
    }; 
}; 

template <> 
class TypeList<void, void> 
{ 
public: 
    enum 
    { 
    position = 0, 
    }; 
}; 


#include <iostream> 

int main() 
{ 
     typedef TypeList< void, void> base; // base 
     typedef TypeList< base, double> t2; // position is unique id for double 
     typedef TypeList< t2, char > t3; // position is unique id for char 

     std::cout << "T1 Posn: " << base::position << std::endl; 
     std::cout << "T2 Posn: " << t2::position << std::endl; 
     std::cout << "T3 Posn: " << t3::position << std::endl; 

} 

Questo funziona, ma naturalmente mi piacerebbe non dover specificare un "prev" digita in qualche modo Preferibilmente trovare un modo per tener traccia di questo automaticamente. Forse ci giocherò ancora un po 'per vedere se è possibile. Sicuramente un puzzle interessante/divertente.

+0

Questa è la stessa soluzione a cui ho pensato, ma non sono mai riuscito a rimuovere quel "prev" . Ma suppongo non c'è modo di salvare questo stato. Da quello che ho capito, questo corrisponde molto alla natura funzionale della metaprogrammazione del modello. – daramarak

7

In linea di principio, questo è possibile, anche se la soluzione probabilmente non è quello che stai cercando.

In breve, è necessario fornire una mappatura esplicita dai tipi ai valori interi, con una voce per ogni tipo possibile:

template< typename T > 
struct type2int 
{ 
    // enum { result = 0 }; // do this if you want a fallback value 
}; 

template<> struct type2int<AClass> { enum { result = 1 }; }; 
template<> struct type2int<BClass> { enum { result = 2 }; }; 
template<> struct type2int<CClass> { enum { result = 3 }; }; 

const int i = type2int<T>::result; 

Se non si fornisce l'implementazione di ripiego nel modello di base , questo fallirà per tipi sconosciuti se T, altrimenti restituirebbe il valore di fallback.

A seconda del contesto, potrebbero esserci anche altre possibilità. Ad esempio, è possibile definire quei numeri all'interno entro i tipi stessi:

class AClass { 
    public: 
    enum { inta_val = 1 }; 
    // ... 
}; 

class BClass { 
    public: 
    enum { inta_val = 2 }; 
    // ... 
}; 

// ... 

template< typename T > 
struct type2int 
{ 
    enum { result = T::int_val }; // will fail for types without int_val 
}; 

Se si dà più contesto, ci potrebbero essere altre soluzioni, anche.

Edit:

In realtà non c'è più contesto ad esso. Stavo esaminando se fosse effettivamente possibile, ma senza assegnare i numeri stessi.

penso che l'idea di Mike di ordinamento è un buon modo per fare questo (ancora una volta, per un insieme fisso di tipi) senza dover assegnare esplicitamente i numeri: sono implicitamente dato dal l'ordinamento. Tuttavia, penso che questo sarebbe più facile utilizzando una lista di tipi. L'indice di qualsiasi tipo nell'elenco sarebbe il suo numero. Penso che qualcosa di simile al seguente potrebbe fare:

// basic type list manipulation stuff 
template< typename T1, typename T2, typename T3...> 
struct type_list; 

// meta function, List is assumed to be some instance of type_list 
template< typename T, class List > 
struct index_of { 
    enum { result = /* find index of T in List */ }; 
}; 

// the list of types you support 
typedef type_list<AClass, BClass, CClass> the_type_list; 

// your meta function 
template< typename T > 
struct type2int 
{ 
    enum { result = index_of<T, the_type_list>::result }; 
}; 
+0

In realtà non c'è più alcun contesto in proposito. Stavo esaminando se fosse effettivamente possibile, ma senza assegnare i numeri stessi – daramarak

+0

Re il 'type_list': avrete bisogno di C++ 0x o estensioni specifiche del compilatore per il modello variadic.Inoltre, è più ordinata della mia soluzione –

+0

@Mike: Potete farlo senza modelli variadici (le liste dei tipi sono state utilizzate per molto tempo), anche se non è così semplice e il numero massimo di tipi è fisso. Infatti, ho aggiunto il '... 'semplicemente per suggerire più parametri, e solo in seguito mi è venuto in mente che potrebbe essere letto come modelli variadici. (Non so nemmeno se questa è la sintassi corretta). – sbi

2

Penso che sia possibile farlo per un set fisso di tipi, ma piuttosto un po 'di lavoro. Dovrai definire una specializzazione per ogni tipo, ma dovrebbe essere possibile utilizzare asserimenti in fase di compilazione per verificare l'univocità.Assumerò un STATIC_ASSERT(const_expr), come quello in Boost.StaticAssert, che causa un errore di compilazione se l'espressione è falsa.

Supponiamo di avere un insieme di tipi che vogliamo ID univoci per - solo 3 per questo esempio:

class TypeA; 
class TypeB; 
typedef int TypeC; 

Ci vorrà un modo per confrontare i tipi:

template <typename T, typename U> struct SameType 
{ 
    const bool value = false; 
}; 

template <typename T> struct SameType<T,T> 
{ 
    const bool value = true; 
}; 

Ora, definiamo un ordinamento di tutti i tipi che vogliamo elencare:

template <typename T> struct Ordering {}; 

template <> struct Ordering<void> 
{ 
    typedef TypeC prev; 
    typedef TypeA next; 
}; 

template <> struct Ordering<TypeA> 
{ 
    typedef void prev; 
    typedef TypeB next; 
}; 

template <> struct Ordering<TypeB> 
{ 
    typedef TypeA prev; 
    typedef TypeC next; 
}; 

template <> struct Ordering<TypeC> 
{ 
    typedef TypeB prev; 
    typedef void next; 
}; 

Ora possiamo definire l'ID univoco:

template <typename T> struct TypeInt 
{ 
    STATIC_ASSERT(SameType<Ordering<T>::prev::next, T>::value); 
    static int value = TypeInt<T>::prev::value + 1; 
}; 

template <> struct TypeInt<void> 
{ 
    static int value = 0; 
}; 

NOTA: non ho provato a compilarlo. Potrebbe essere necessario aggiungere typename in alcuni punti e potrebbe non funzionare affatto.

Non si può sperare di mappare tutti i tipi possibili in un campo intero, perché ne esiste un numero illimitato: tipi di puntatore con livelli arbitrari di indiretta, tipi di array di dimensione e rango arbitrari, tipi di funzione con numeri arbitrari di argomenti, e così via.

+0

Domanda stupida da metaprogrammer non modello. Come viene risolto TypeA/TypeB/TypeC quando non è specificato come parametro o specializzazione del modello? Sembra che in qualche modo questi debbano essere vincolati (implicitamente in qualche modo o esplicitamente). Non vedo come viene fatto qui o come sarebbe stato fatto. Ho provato a compilare il codice e ottengo errori su Tipo (A | B | C) non definiti, e non sono sicuro di come risolverlo. –

+0

Sono i tipi che si desidera enumerare (ad esempio "AClass" nella domanda). Ho aggiunto dichiarazioni di esempio alla risposta. –

+0

@ Mike: Mi piace la tua idea di ordinare. Tuttavia, non credi che mettere i tipi in una lista di tipi e usare i loro indici in quella lista di tipi sarebbe molto più semplice? – sbi

0

Non penso sia possibile senza assegnare i numeri da soli o avere un singolo file a conoscenza di tutti i tipi. E anche in questo caso ti imbatterai in problemi con le classi template. Devi assegnare il numero per ogni possibile istanziazione della classe?

+0

Ho pensato al problema in questo modo: solo i tipi che utilizzano TypeToInt devono essere dato un numero. – daramarak

4

Questo fa quello che vuoi. I valori sono assegnati in base alle necessità. Sfrutta il modo in cui vengono assegnate le statiche nelle funzioni.

inline size_t next_value() 
{ 
    static size_t id = 0; 
    size_t result = id; 
    ++id; 
    return result; 
} 

/** Returns a small value which identifies the type. 
    Multiple calls with the same type return the same value. */ 
template <typename T> 
size_t get_unique_int() 
{ 
    static size_t id = next_value(); 
    return id; 
} 

Non è metaprogrammazione modello su steroidi, ma io conto che come una buona cosa (credetemi!)

+2

Ma questo non genererà i valori in fase di compilazione, che sono lo scopo di questa attività. – daramarak

+0

Non penso che sia sicuro. So che l'inizializzazione statica stessa è thread-safe, ma get_unique_int potrebbe essere chiamato per la prima volta su due tipi diversi su due thread differenti e next_value verrebbe chiamato simultaneamente. – enobayram

1

Questo può essere fare alcune "cose ​​cattive" e, probabilmente, viola lo standard in qualche modo sottile .. ma pensavo di condividere comunque ... forse qualcun altro può sanitarlo in qualcosa di legale al 100%? Ma sembra che funzioni sul mio compilatore.

La logica è questa .. costruire una funzione membro statico per ogni tipo che ti interessa e prendere il suo indirizzo. Quindi converti quell'indirizzo in un int. I bit che sono un po 'sospetti sono: 1) la conversione da ptr a int funzione. e 2) Non sono sicuro che lo standard garantisca che gli indirizzi delle funzioni membro statiche si fonderanno correttamente per gli usi in diverse unità di compilazione.

typedef void(*fnptr)(void); 

union converter 
{ 
    fnptr f; 
    int i; 
}; 

template<typename T> 
struct TypeInt 
{ 
    static void dummy() {} 
    static int value() { converter c; c.f = dummy; return c.i; } 
}; 

int main() 
{ 
    std::cout<< TypeInt<int>::value() << std::endl; 
    std::cout<< TypeInt<unsigned int>::value() << std::endl; 
    std::cout<< TypeInt< TypeVoidP<int> >::value() << std::endl; 
} 
+0

Si noti inoltre che per molti usi in cui è sufficiente confrontare gli interi che si possono ottenere senza la conversione dal puntatore alla funzione int, basta confrontare i puntatori di funzione con la funzione fittizia. –

+0

Ma questo non ti darà un tipo unico al momento della compilazione, vero? – daramarak

+0

Corretto, l'int da questa non è una costante di tempo di compilazione. Comunque penso che l'indirizzo di TypeInt :: dummy sia una costante temporale di compilazione, se è sufficiente. –

0

type2int come costante di tempo di compilazione impossibile anche in C++ 11. Forse un ragazzo ricco dovrebbe promettere una ricompensa per l'ansiere? Fino ad allora sto usando la soluzione seguente, che è sostanzialmente uguale a Matthew Herrmann di:

class type2intbase { 
    template <typename T> 
    friend struct type2int; 

    static const int next() { 
     static int id = 0; return id++; 
    } 
}; 

template <typename T> 
struct type2int { 
    static const int value() { 
     static const int id = type2intbase::next(); return id; 
    } 
}; 

Nota anche

template <typename T> 
struct type2ptr { 
    static const void* const value() { 
     return typeid(T).name(); 
    } 
}; 
2

Io non sono a conoscenza di un modo per mappare una fase di compilazione costante intera ad un tipo, ma posso darti la cosa migliore. Questo esempio dimostra un modo per generare un identificatore univoco per un tipo che - mentre non è un'espressione costante integrale - verrà generalmente valutato in fase di compilazione. È anche potenzialmente utile se hai bisogno di un mapping tra un tipo e un argomento di modello non di tipo univoco.

struct Dummy 
{ 
}; 

template<typename> 
struct TypeDummy 
{ 
    static const Dummy value; 
}; 

template<typename T> 
const Dummy TypeDummy<T>::value = Dummy(); 

typedef const Dummy* TypeId; 

template<typename T, TypeId p = &TypeDummy<T>::value> 
struct TypePtr 
{ 
    static const TypeId value; 
}; 

template<typename T, TypeId p> 
const TypeId TypePtr<T, p>::value = p; 

struct A{}; 

struct B{}; 

const TypeId typeA = TypePtr<A>::value; 
const TypeId typeB = TypePtr<B>::value; 

ho sviluppato questo come una soluzione per i problemi di prestazioni con i tipi di ordinazione utilizzando typeid(A) == typeid(B), che un certo compilatore non riesce a valutare in fase di compilazione. È anche utile poter memorizzare i valori TypeId per il confronto in fase di esecuzione: ad es. someType == TypePtr<A>::value

+1

Può essere usato in modo sicuro in una libreria condivisa? Se ho due librerie condivise A e B che accedono entrambe al TypePtr :: value, posso essere sicuro che ottengano lo stesso valore? – enobayram

+0

@enobayram Sembra un comportamento definito dall'implementazione. Dubito fortemente che qualsiasi implementazione esistente garantisca lo stesso valore su tutte le librerie condivise. – willj

6

Simile all'approccio di Michael Anderson, ma questa implementazione è pienamente conforme agli standard e può essere eseguita in fase di compilazione. A partire da C++ 17 sembra che i valori di constexpr possano essere usati come parametro di modello per altri scopi di meta-programmazione del modello. Anche unique_id_type può essere confrontato con ==,! =,>, <, ecc. Per scopi di smistamento.

// the type used to uniquely identify a list of template types 
typedef void (*unique_id_type)(); 

// each instantiation of this template has its own static dummy function. The 
// address of this function is used to uniquely identify the list of types 
template <typename... Arguments> 
struct IdGen { 
    static constexpr inline unique_id_type get_unique_id() 
    { 
     return &IdGen::dummy; 
    } 

private: 
    static void dummy(){}; 
}; 
+2

Può essere utilizzato in modo sicuro in una libreria condivisa? Se ho due librerie condivise A e B che chiamano entrambe IdGen :: get_unique_id(), posso essere sicuro che ottengano lo stesso valore? – enobayram

+1

Non funzionerà sicuramente in una so/dll. Abbiamo riscontrato un problema simile con il nostro sistema RTTI, che utilizza una tecnica simile per la creazione di ID di classe. – lhumongous

Problemi correlati