2010-04-13 35 views
10

E 'possibile creare una funzione di modello che prende un numero variabile di argomenti, ad esempio, in questo costruttore Vector< T, C > classe:C++ Template costruttore di classe con argomenti variabili

template < typename T, uint C > 
Vector< T, C >::Vector(T, ...) 
{ 
    va_list arg_list; 
    va_start(arg_list, C); 
    for(uint i = 0; i < C; i++) { 
     m_data[ i ] = va_arg(arg_list, T); 
    } 
    va_end(arg_list); 
} 

Questo quasi funziona, ma se qualcuno chiama Vector< double, 3 >(1, 1, 1), solo il primo argomento ha il valore corretto. Sospetto che il primo parametro sia corretto perché durante il richiamo della funzione viene eseguito il cast a double e gli altri sono interpretati come int s e quindi i bit vengono inseriti in un double. Chiamando Vector< double, 3 >(1.0, 1.0, 1.0) si ottengono i risultati desiderati. C'è un modo preferito per fare qualcosa di simile?

+2

Si noti che la sintassi di inizializzazione universale C++ 11 di questo vi darà in modo sicuro. – sbi

risposta

2

Questo codice è pericoloso e penso che la vostra analisi sul perché non sta funzionando è a posto, non c'è modo per il compilatore a sapere che al momento della chiamata:

Vector< double, 3 >(1, 1, 1) 

quelli dovrebbe essere passato come doppie .

vorrei cambiare il costruttore a qualcosa di simile:

Vector< T, C >::Vector(const T(&data)[C]) 

invece, e hanno l'utente passare gli argomenti come un array. Un altro tipo di brutto soluzione potrebbe essere qualcosa di simile:

template < typename T, uint C > 
Vector< T, C >::Vector(const Vector<T, C - 1>& elements, T extra) { 
} 

e chiamare in questo modo (con alcune typedef):

Vector3(Vector2(Vector1(1), 1), 1); 
+2

Oppure 'T (& data) [C]' e l'utente passa gli argomenti come matrice per riferimento. È meno flessibile in quanto non consente l'utilizzo di array dinamici, ma può aiutare a far rispettare il numero corretto di argomenti. –

+0

@Chris Good point –

+2

Inoltre, con il secondo suggerimento, assicurati di specializzare il modello per 'C = 0' e/o' C = 1'. –

0

In C++ 0x (in realtà dovrebbe essere chiamato C++ 1x), è possibile utilizzare template varargs per ottenere ciò che si desidera in un typesafe moda (e non avrai nemmeno bisogno di specificare il numero di argomenti!). Tuttavia, nell'attuale versione di C++ (ISO C++ 1998 con modifiche del 2003), non c'è modo di realizzare ciò che si desidera. Puoi tenere a bada o fare ciò che fa Boost, che usa preprocessor macro magic per ripetere la definizione del costruttore più volte con diversi numeri di parametri fino a un limite hardcoded, ma di grandi dimensioni. Dato che è una specie di Boost.Preprocessor complicando, si può solo definire tutti i seguenti soli:

 
Vector<T,C>::Vector(); 
Vector<T,C>::Vector(const T&); 
Vector<T,C>::Vector(const T&, const T&); 
// ... 

Da quanto sopra è una specie di doloroso da fare a mano, però, si potrebbe scrivere uno script per generarlo.

+2

Tutti sanno che la "x" è una cifra esadecimale ;-) –

9

Ahimè, in questo momento non c'è buon modo per farlo. La maggior parte dei pacchetti Boost che hanno bisogno di fare qualcosa di simile uso trucchi macro per definire le cose in questo modo:

template < typename T > 
Vector<T>::Vector(T) 
{ ... } 

template < typename T, uint C > 
Vector< T, C >::Vector(T t, C c1) 
{ ... } 

template < typename T, uint C > 
Vector< T, C >::Vector(T t, C c1, C c2) 
{ ... } 

template < typename T, uint C > 
Vector< T, C >::Vector(T t, C c1, C c2, C c3) 
{ ... } 

Le macro generano un certo numero prestabilito (in genere circa 10) le versioni, e fornire un meccanismo per cambiare il numero massimo di parametri prima di espandere la costruzione.

Fondamentalmente, è un vero dolore, motivo per cui C++ 0x sta introducendo argomenti di template di lunghezza variabile e metodi di delega che ti permetteranno di farlo in modo pulito (e sicuro). Nel frattempo puoi farlo con macro o provare un compilatore C++ che supporti (alcune di) queste nuove funzionalità sperimentali. GCC è buono per questo.

Attenzione però dal momento che C++ 0x non è ancora uscito, le cose possono ancora cambiare e il tuo codice potrebbe non essere sincronizzato con la versione finale dello standard. Inoltre, anche dopo lo standard, ci saranno circa 5 anni durante i quali molti compilatori supportano solo parzialmente lo standard, quindi il tuo codice non sarà molto portabile.

+0

Scopri Boost.Preprocessore se vai giù nel modo macro. Il cumulo di 'BOOST_PP_REPEAT' e' BOOST_PP_ENUM_TRAILING_PARAMS' dovrebbe metterti sulla giusta strada. –

+0

Grazie. Avevo fretta quando ho postato quanto sopra, quindi non sono andato a cercare cosa fossero le macro di Preprocessore Boost. – swestrup

2

Si può fare ciò che si vuole, ma non farlo, perché non è tipologico. Meglio passare un vettore di T o una coppia di iteratori che contengono quei valori.

template < typename T, uint C > 
Vector< T, C >::Vector(int N, ...) 
{ 
    assert(N < C && "Overflow!"); 
    va_list arg_list; 
    va_start(arg_list, N); 
    for(uint i = 0; i < N; i++) { 
     m_data[i] = va_arg(arg_list, T); 
    } 
    va_end(arg_list); 
} 

Vector<int> v(3, 1, 2, 3); 

Questo può essere meglio risolti, poiché tutti gli elementi sono omogenei digitato comunque.

template < typename Iter, uint C > 
Vector< T, C >::Vector(Iter begin, Iter end) 
{ 
    T *data = m_data; 
    while(begin != end) 
     *data++ = *begin++; 
} 

int values[] = { 1, 2, 3 }; 
Vector<int> v(values, values + 3); 

Naturalmente, è necessario assicurarsi che ci sia abbastanza posto in m_data.

+0

Se abbiamo già 'template ' e stiamo già facendo in modo che l'utente dichiari una matrice 'int valori []', potrebbe non essere più sicuro (in termini di dimensioni) che il costruttore sia 'Vector < T, C > :: Vector (T (& const) [C]) '? Oltre a consentire fette di un array o di un array dinamico, c'è qualche ragione per cui non dovremmo permettere loro di passare semplicemente un array? –

+0

@Chris sì, potremmo farlo. Ho scoperto che mi piace mostrare la gamma di iteratori, dato che è il modo "ufficiale" utilizzato anche dai contenitori ecc. Puoi anche fare 'Vector v (begin (valori), end (valori));' per non dover conta tu stesso gli elementi, con le funzioni correttamente definite 'end' e' begin' (da boost.range) –

0

std::tr1::array (che è simile al vostro) non definisce un costruttore, e può essere inizializzato come un aggregato (?)

std::tr1::array<int, 10> arr = {{ 1, 2, 3, 4, 5, 6 }}; 

Inoltre si potrebbe verificare Boost.Assignment biblioteca.

Per esempio il costruttore potrebbe essere

template < typename T, uint C > 
template < typename Range > 
Vector< T, C >::Vector(const Range& r) 

e istanze create con

Vector<int, 4> vec(boost::assign::cref_list_of<4>(1)(3)(4)(7)); 
0

È possibile utilizzare variadic, variadic significa modello con argomenti variabili. more

0

Il problema con argomenti variabili nei costruttori è:

  • è necessario il cdecl convenzione di chiamata (o un altro in grado di gestire varargs)
  • non puoi definire cdecl per un costruttore (in MSVS)

Così il codice "corretto" (MS) potrebbe essere:

template < typename T, uint C > __cdecl Vector< T, C >::Vector(T, ...) 

ma il compilatore dirà:

illegale convenzione di chiamata per il costruttore/distruttore (MS C4166)

Problemi correlati