2012-01-06 17 views
9

Stavo leggendo le risposte a "Printing 1 to 1000 without loop or conditionals" e mi chiedo perché è necessario avere il caso speciale per NumberGeneration < 1> nella risposta superiore.Ricompilazione e condizionali del tempo di compilazione

Se rimuovo e aggiungo un controllo per N == 1 nel modello (codice di seguito), la compilazione del codice non riesce con "profondità di istanziazione modello supera il massimo", ma non sono sicuro del perché. I condizionali sono gestiti diversamente in fase di compilazione?

#include <iostream> 

template<int N> 
struct NumberGeneration 
{ 
    static void out(std::ostream& os) 
    { 
     if (N == 1) 
     { 
      os << 1 << std::endl; 
     } 
     else 
     { 
      NumberGeneration<N-1>::out(os); 
      os << N << std::endl; 
     } 
    } 
}; 

int main() 
{ 
    NumberGeneration<1000>::out(std::cout); 
} 

risposta

12

La generazione e la compilazione del codice non si dirama in base ai condizionali! Considerate questo:

// don't declare bar()! 

void foo() 
{ 
    if (false) { bar(); } 
} 

Se non dichiara bar(), questo è un errore di compilazione anche se la portata interna non può mai essere raggiunta. Per lo stesso motivo, NumberGeneration<N-1> viene sempre istanziato, non importa se quel ramo può essere raggiunto o meno, e si ha una ricorsione infinita.

Infatti, l'analogo statica dei condizionali è proprio specializzazione di modello:

template <> struct NumberGeneration<0> { /* no more recursion here */ }; 
+0

Grazie, questo ha senso, non stavo davvero pensando all'istanziazione in fase di compilazione ed era aspettandosi che venga fatto solo quando viene raggiunto il codice. –

+2

@ baris.m: Ma c'è una sottigliezza cruciale di ciò che intendi con "il codice viene raggiunto": viene raggiunto una volta durante la compilazione dal compilatore, sempre, e quindi di nuovo * condizionalmente * in fase di esecuzione durante l'esecuzione del programma. –

+0

Nel mio commento stavo parlando di essere raggiunto in fase di esecuzione. La tua risposta ha perfettamente senso. –

0

È perché gli interi possono essere negativi e codice runtime (l'assegno if) non fermeranno il compilatore un'istanza il modello con 0, -1, -2, ecc Un compilatore potrebbe essere in grado di ottenere via con quello che proponi, ma cosa succede se istanziare gli altri template (0, -1, ...) ha effetti collaterali da cui dipende? In questo caso, il compilatore non può fallire nell'istanziarli per te.

In breve, proprio come alla ricorsione è necessario fornire il proprio caso base.

1

Mi chiedo il motivo per cui è necessario avere la cassa speciale per NumberGeneration < 1> nella risposta superiore.

Perché questa è la condizione di fine per ricorsiva! Senza questo, come potrebbe la fine ricorsiva?

+0

E il condizionale if == 1 che ho aggiunto al modello? In tal caso, non ne istanziamo uno nuovo con N-1, quindi è così che mi aspetto che la ricorsione si fermi. –

+0

@ baris.m: non è necessario rimuovere la specializzazione modello per la condizione 1 poiché questa è la condizione finale per la compilazione per terminare la compilazione/compilazione. – Gob00st

+1

@ baris.m: la condtion che aggiungi non aiuta perché alla compilazione non interessa e termina la compilazione durante la compilazione. Il controllo che hai aggiunto è utile solo in fase di esecuzione, NON in fase di compilazione. – Gob00st

4

Il condizionale if non verranno trattati in fase di compilazione. Sarà gestito in fase di runtime.

Quindi, anche per N = 1, il compilatore genera NumberGenerator < 0>, quindi NumberGenerator < -1> ... all'infinito, fino a raggiungere la profondità dell'istanza di modello.

1

In generale, la condizione N == 1 nel codice viene valutata in fase di esecuzione (sebbene il compilatore possa ottimizzare questo valore), non al momento della compilazione. Pertanto la ricorsione dell'istanziazione del modello nella clausola else non viene mai terminata. NumberGeneration<1> d'altra parte viene valutato al momento della compilazione e quindi agisce come un caso di terminazione di questo modello ricorsivo.

1

Sono abbastanza sicuro che questo è specifico del compilatore; alcuni compilatori potrebbero provare a generare entrambi i rami di if/else indipendentemente dal valore di N, nel qual caso la compilazione non riuscirà in nessun caso. Altri compilatori possono valutare la condizione al momento della compilazione e generare solo il codice per il ramo che viene eseguito, nel qual caso la compilazione avrà esito positivo.

AGGIORNAMENTO: O come dice Luc nei commenti, può darsi che il compilatore debba generare entrambi i rami, in modo che il codice fallisca sempre. Non sono abbastanza sicuro di quale sia il caso, ma in entrambi i casi è una cattiva idea affidarsi a condizioni di run-time per controllare la generazione del codice in fase di compilazione.

Sarebbe meglio usare la specializzazione:

template <int N> 
struct NumberGeneration 
{ 
    static void out(std::ostream & os) 
    { 
     NumberGeneration<N-1>::out(os); 
     os << N << std::endl; 
    } 
}; 

template <> 
void NumberGeneration<1>::out(std::ostream & os) 
{ 
    os << 1 << std::endl; 
} 

(o si potrebbe ridurre questo un po 'da parte, invece specializzata per N=0, con una funzione di out che non fa nulla).

Inoltre, tenere presente che alcuni compilatori potrebbero non supportare modelli profondamente resursivi; C++ 03 suggerisce una profondità minima supportata di soli 17, che aumenta C++ 11 a 1024. Si dovrebbe verificare quale sia il limite del compilatore.

+0

Hum, penso che il compilatore * debba * istanziare il modello anche se potrebbe rilevare che il codice non sarà mai raggiunto in fase di runtime, quindi non penso che questo codice possa mai finire la compilazione, qualunque compilatore tu usi. –

+2

Non è necessario specializzare l'intera struttura, è possibile farlo solo per la funzione. –

+0

@PaulManta: In effetti puoi farlo. Grazie. –

3

I modelli vengono istanziati al momento della compilazione, il caso speciale del modello impedisce al compilatore di ricorsivamente inferiore a 1 durante la compilazione.

le clausole if vengono valutate in fase di esecuzione, quindi il compilatore ha già fallito la compilazione del codice quando avrebbe avuto alcun effetto.

0

Ecco il modo corretto per farlo:

template<int N> 
struct NumberGeneration 
{ 
    static void out(std::ostream& os); 
}; 

template<int N> 
void NumberGeneration<N>::out(std::ostream& os) 
{ 
    NumberGeneration<N-1>::out(os); 
    os << N << std::endl; 
} 

template<> 
void NumberGeneration<1>::out(std::ostream& os) 
{ 
    os << 1 << std::endl; 
} 

int main() 
{ 
    NumberGeneration<20>::out(std::cout); 
} 

che si chiama modello di specializzazione: si, il programmatore, forniscono una definizione alternativa per una particolare istanza di un modello. Puoi specializzare l'intero modello, o solo una parte di esso, come ho fatto qui (ho solo specializzato la funzione, non l'intera struttura).

Problemi correlati