2015-12-09 16 views
6

Supponiamo le seguenti classi politiche che si prendono cura di un aspetto di un algoritmo:Evitando ramificazione in base al valore di ritorno della funzione che è un argomento di template

struct VoidF { 
    static void f() { 
     ... // some code that has side effects 
    } 
}; 

struct BoolF { 
    static bool f() { 
     bool res = ...; // some computation 
     return res; 
    } 
}; 

La politica BoolF è "la valorizzazione-aware": quando BoolF :: f() restituisce true, l'algoritmo può uscire. VoidF è "enhancement-inconsapevole", quindi restituisce void (non voglio forzare l'utente della mia libreria a restituire bool quando non significa niente per lui).

L'algoritmo è attualmente scritto in questo modo:

template <typename F> 
struct Algorithm { 
    void run() { 
     ... // some computation here 

     if (std::is_same<decltype(F::f()), bool>::value) { 
      if (F::f()) return; 
     } else 
      F::f(); // If F is VoidF, there should be no branching and some 
        // compiler optimizations will be enabled 

     ... // more computation, unless F::f() got rid of it 
    } 
}; 

Naturalmente, questo non funziona se Algorithm è istanziato con VoidF. C'è un modo per risolvere questo in modo che non ci dovrebbero essere ramificazioni in Algorithm<VoidF>::run() come indicato dal commento?

+2

ne dite 'run_method (f, std :: is_same {});' , con un 'run_method' opportunamente sovraccarico, ovviamente. Questo è noto come invio di tag di tipo. –

+0

Una volta che il codice funziona, non ci saranno ramificazioni su nessun compilatore moderno. 'std :: is_same <> :: value' è una costante in fase di compilazione. Poiché ti preoccupi della ramificazione, un problema che è presente nell'output dell'assieme, non puoi chiedere come evitarlo senza prima esaminare il suddetto output dell'assieme. –

+0

Puoi considerare uno spazio nomi invece di una struct e tag dispatching –

risposta

2

È necessario utilizzare SFINAE anziché la ramificazione in fase di esecuzione.

si funzione run dovrebbe essere simile a questo:

template <typename F> 
struct Algorithm { 

    void run() { 
     ... // some computation here 

     doRun(); 
    } 

    template<std::enable_if_t<std::is_same<decltype(F::f()), bool>::value, int> = 0> 
    void doRun() { 
     if (F::f()) { 
      // do some more computations if needed or simply remove the if and return 
     } 
    } 

    template<std::enable_if_t<!std::is_same<decltype(F::f()), bool>::value, int> = 0> 
    void doRun() { 
     F::f(); 

     ... // more computation, unless F::f() got rid of it 
    } 
}; 
+0

Ciò significa che devo eliminare tutte le altre cose dalla funzione 'run' per evitare la duplicazione del codice, il che è scomodo, richiederà più passaggi di argomenti e potrebbe precludere le ottimizzazioni del compilatore se le cose non sono rientrate in ... c'è un modo per andarsene senza avere due funzioni 'run' diverse? – AlwaysLearning

+0

Non ripeterti, questo è sicuro. Il compilatore sarà in grado di ottimizzare la chiamata della funzione supplementare, soprattutto se si utilizza l'inoltro perfetto per i propri argomenti. –

+0

Modificata la risposta, dovrebbe adattarsi meglio al tuo codice ora –

0

Una variante di answer Guillaume Racicot:

template <typename F> 
struct Algorithm { 

    void run() { 
     ... // some computation here 

     if(doRun()) 
      return; 

     ... // more computation, unless F::f() got rid of it 
    } 

    template<std::enable_if_t<std::is_same<decltype(F::f()), bool>::value, int> = 0> 
    bool doRun() { 
     return F::f(); 
    } 

    template<std::enable_if_t<!std::is_same<decltype(F::f()), bool>::value, int> = 0> 
    bool doRun() { 
     F::f(); 
     return false; 
    } 
}; 

Il tipo di F è noto al momento della compilazione. Quindi nel caso di VoidF il compilatore sa che doRun restituirà sempre false e quindi dovrebbe rimuovere tutto il codice di diramazione durante l'ottimizzazione.

Aggiornamento: Quindi non è garantito che non ci sarà alcuna filiale. Ma dovrebbe essere un caso piuttosto facile per l'ottimizzatore.

+0

Con l'invio di tag potrebbe essere più semplice chiamare una funzione di supporto per semplificare il vincolo del modello. Ad esempio: http://coliru.stacked-crooked.com/a/03a86e336355c50e Tuttavia, questo approccio si ramifica sempre, che è il problema che l'OP sta cercando di risolvere. Buon punto a proposito dell'ottimizzatore, comunque. – AndyG

+0

Questo è quello che volevo fare in origine. Vedere il mio post precedente: http://stackoverflow.com/q/34185286/2725810 – AlwaysLearning

3

Ecco il mio tentativo di farlo senza SFINAE:

template <typename F> 
struct Algorithm { 
    void run() { 
     ... // some computation here 

     myRun(std::integral_constant< 
       bool, std::is_same<decltype(F::f()), bool>::value>()); 
    } 

private: 
    void myRun(std::true_type) { 
     if (F::f()) return; 
     moreComputation(); 
    } 

    void myRun(std::false_type) { 
     F::f(); 
     moreComputation(); 
    } 

    void moreComputation() { ... } 
}; 
+1

è possibile capovolgerlo e utilizzare 'is_void' per accorciare un po '. –

Problemi correlati