2012-12-11 11 views
15

È possibile che esista qualcosa del genere?È possibile sviluppare il ciclo statico per il ciclo in C++?

template<int Channel> 
void deduce_mask(Matrix const &src, int mask[]) 
{ 
    //I hope i could become a constant and the compiler would unroll the loop at compile time   
    for(int i = Channel; i != -1; --i) 
    {    
     //mapper is a helper class which translate two and three dimension into one dimension index 
     //constexpr makes it possible to find out the index at compile time 
     mask[mapper(0, 1, i)] = src(row - 1, col)[i]; 
     mask[mapper(1, 1, i)] = src(row, col)[i]; 
     mask[mapper(2, 1, i)] = src(row + 1, col)[i];  
    } 
} 

invece di

template<int Channel> 
class deduceMask 
{ 
public: 
    static void deduce_mask(matrix const &src, int mask[]); 
}; 

template<int Channel> 
void deduce_mask(matrix const &src, int mask[]) 
{     
    mask[mapper(0, 1, Channel)] = src(row - 1, col)[Channel]; 
    mask[mapper(1, 1, Channel)] = src(row, col)[Channel]; 
    mask[mapper(2, 1, Channel)] = src(row + 1, col)[Channel];  

    deduceMask<Channel - 1>::deduce_mask(src, mask); 
} 

template<> 
class deduceMask<-1> 
{ 
public: 
    static void deduce_mask(matrix const &src, int mask[]) 
    { 

    } 
}; 

La seconda soluzione è l'unica soluzione che ho potuto venire di quando voglio che il compilatore di capire il risultato in fase di compilazione time.Do Ho un modo semplice per rendere la "i" un valore costante come la soluzione di metaprogrammazione? Per me, un ciclo for semplice è molto più semplice da utilizzare piuttosto che la versione metaprogrammazione dello .

Ci scusiamo per il mio scarso inglese, spero di spiegare correttamente il mio problema.

+2

Si potrebbe anche scrivere in modo ricorsivo e utilizzare constexpr, se si preferisce quel tipo di sintassi? – Agentlien

+0

Ho provato a creare una versione di constexpr ma non è riuscito, constexpr consente solo un'istruzione return. – StereoMatching

+3

sono abbastanza certo che la maggior parte dei compilatori moderni fanno questa ottimizzazione automaticamente, proprio come lo fanno per 'for' loop fino a un valore costante (ad esempio' for (int i = 0; i <5; i ++) '). Dovresti controllare per essere sicuro. – ShdNx

risposta

20

La metaprogrammazione del modello in C++ è pura programmazione funzionale, e nella pura programmazione funzionale non si utilizzano i loop come per o mentre non si ottiene alcun dato mutabile. Tutto ciò che hai è ricorsione. Per semplificare il lavoro con la ricorsione, è necessario aumentare un po 'il livello di astrazione. Il codice ricorsivo che avete va bene, ma l'iterazione e il lavoro può essere suddiviso a parte:

template <int First, int Last> 
struct static_for 
{ 
    template <typename Fn> 
    void operator()(Fn const& fn) const 
    { 
     if (First < Last) 
     { 
      fn(First); 
      static_for<First+1, Last>()(fn); 
     } 
    } 
}; 

template <int N> 
struct static_for<N, N> 
{ 
    template <typename Fn> 
    void operator()(Fn const& fn) const 
    { } 
}; 

Ora che avete questa meta-funzione, è possibile scrivere la funzione deduce_mask in questo modo:

template<int Channel> 
void deduce_mask(Matrix const &src, int mask[]) 
{ 
    static_for<0, Channel>()([&](int i) 
    {    
     mask[mapper(0, 1, i)] = src(row - 1, col)[i]; 
     mask[mapper(1, 1, i)] = src(row, col)[i]; 
     mask[mapper(2, 1, i)] = src(row + 1, col)[i];  
    }); 
} 

Visual C++ 2012 con Ob1 interruttore/riga di comando compila questo codice in questo:

push  0 
call  <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h) 
push  1 
call  <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h) 
push  2 
call  <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h) 
push  3 
call  <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h) 
push  4 
call  <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h) 
... 

Se non è possibile utilizzare le funzioni lambda, è necessario scrivere un funtore. Functor ha un vantaggio rispetto alla funzione lambda: puoi specificare una convenzione di chiamata (se non ti dispiace farlo). Se l'operatore() del functor dispone della convenzione di chiamata __fastcall, verrà visualizzato mov edx, x anziché push x nel codice assembler.

+0

Grazie, questa risposta è piuttosto elegante (almeno per me) – StereoMatching

+5

Ma non sono tutte quelle 'call's più lento di avere un normale ciclo for? Perché il compilatore non li ottimizza? – Kapichu

2

La risposta di lego, elegante e straordinaria, non verrà compilata se si desidera che l'indice entri in un modello, ad es. std::get<i>(some_tuple)

Nel caso in cui si desidera implementare questa funzionalità aggiuntiva, in futuro, il codice qui sotto funziona e dovrebbe essere retrocompatibile con la soluzione di lego (se non che io uso un metodo statico applicare al posto dell'operatore()):

template <int First, int Last> 
struct static_for 
{ 
    template <typename Lambda> 
    static inline constexpr void apply(Lambda const& f) 
    { 
     if (First < Last) 
     { 
      f(std::integral_constant<int, First>{}); 
      static_for<First + 1, Last>::apply(f); 
     } 
    } 
}; 
template <int N> 
struct static_for<N, N> 
{ 
    template <typename Lambda> 
    static inline constexpr void apply(Lambda const& f) {} 
}; 

Ora è possibile effettuare le seguenti operazioni:

static_for<0, Channel>::apply([&](auto i) // Changed from '(int i)'. In general, 'auto' will be a better choice for metaprogramming! 
{    
    // code... 
    mask[mapper(0, 1, i)] = src(row - 1, col)[i]; // Notice that this does not change 
    std::get<i.value>(some_tuple); // But here you must get the member .value 
    // more code... 
}); 

Testato in VC++ 2015 non ho ricerco il motivo per cui questo funziona, ma posso solo supporre che std::integral_constant<T,...> definisce un cast implicito a T utilizzando va lue, ma il compilatore non può capire che il cast implicito produce un constexpr, quindi è necessario recuperare il valore usando i.value, che è un constexpr.

Rivolgendosi @ domanda di Tom nel commento Se si desidera iterare un parametro pack, è possibile effettuare le seguenti (stessa implementazione):

template<typename... Args> 
inline constexpr auto foo(const Args&... args) 
{ 
    static_for<0,sizeof...(Args)>::apply([&](auto N) 
    { 
     std::cout << std::get<N.value>(std::make_tuple(args...)); 
    }); 
} 

foo(1,"a",2.5); // This does exactly what you think it would do 

Se std::get<N.value>(std::make_tuple(args...)) sembra brutto, è possibile creare un altro constexpr funzione che minimizza il codice.

+0

Penso che sia necessario cambiare '(int i)' a '(auto i)', perché 'int :: value' è mal formato – Caleth

+0

@Caleth Buona cattura! Un risultato di copia/incolla da più fonti. – AOK

+0

Incredibile! Questo rende il mio codice molto più leggibile. – tom

2

Con if constexpr possiamo migliorare la soluzione di AOK.

template <int First, int Last, typename Lambda> 
inline void static_for(Lambda const& f) 
{ 
    if constexpr (First < Last) 
     { 
     f(std::integral_constant<int, First>{}); 
     static_for<First + 1, Last>(f); 
     } 
} 

Con questo possiamo sbarazzarci di quella ::apply

static_for<0, Channel>([&](auto i) 
{    
    // code... 
    mask[mapper(0, 1, i)] = src(row - 1, col)[i]; // Notice that this does not change 
    std::get<i.value>(some_tuple); // But here you must get the member .value 
    // more code... 
}); 

Purtroppo si deve ancora scrivere i.value.


Nota che questo non sarebbe possibile senza if constexpr perché la via di AOK richiederebbe modello di specializzazione parziale di static_for.