2010-04-14 11 views
6

Sono riluttante a dire che non riesco a capirlo, ma non riesco a capirlo. Ho cercato su Google e cercato Stack Overflow e sono uscito vuoto.Funzioni membro della classe istanziate da tratti [politiche, in realtà]

La forma astratta e forse eccessivamente vaga della domanda è, , come posso utilizzare il modello di tratti per creare istanze di membri?[Aggiornamento: ho usato il termine sbagliato qui. Dovrebbero essere "politiche" piuttosto che "tratti". I tratti descrivono le classi esistenti. Le politiche prescrivono le lezioni sintetiche.] La domanda è emersa mentre modernizzava una serie di ottimizzatori di funzioni multivariati che ho scritto più di 10 anni fa.

Gli ottimizzatori funzionano tutti selezionando un percorso rettilineo attraverso lo spazio dei parametri lontano dal punto migliore corrente ("aggiornamento"), quindi individuando un punto migliore su tale linea (la "ricerca di riga"), quindi eseguendo il test per la condizione "fatto", e se non fatto, iterando.

Esistono diversi metodi per eseguire l'aggiornamento, la ricerca di riga e, in teoria, per il test eseguito e altre cose. Mescolare e abbinare. Diverse formule di aggiornamento richiedono dati variabili variabili diversi. Ad esempio, l'aggiornamento LMQN richiede un vettore e l'aggiornamento BFGS richiede una matrice. Se la valutazione dei gradienti è economica, la ricerca di riga dovrebbe farlo. In caso contrario, dovrebbe utilizzare solo le valutazioni delle funzioni. Alcuni metodi richiedono ricerche di linea più accurate di altri. Questi sono solo alcuni esempi.

La versione originale crea molte delle combinazioni tramite funzioni virtuali. Alcuni tratti sono selezionati impostando i bit della modalità testati in fase di esecuzione. Che schifo. Sarebbe banale definire i tratti con # define e le funzioni membro con # ifdef e macro. Ma è così vent'anni fa. Mi infastidisce il fatto che non riesca a capire un modo moderno.

Se ci fosse un solo tratto che variava, potrei usare il modello di template che ricorre curiosamente nello . Ma non vedo alcun modo per estenderlo a combinazioni arbitrarie di tratti.

Ho provato a farlo utilizzando boost::enable_if, ecc. Le informazioni di stato specializzate erano facili. Sono riuscito a svolgere le funzioni, ma solo ricorrendo a funzioni esterne non amichevoli che hanno come parametro il parametro this. Non ho mai nemmeno capito come rendere le funzioni amiche, e tanto meno le funzioni membro. Il compilatore (VC++ 2008) si è sempre lamentato del fatto che le cose non corrispondevano. Io griderei: "SFINAE, idiota!" ma il deficiente è probabilmente me.

Forse la spedizione dei tag è la chiave. Non ci sono riuscito molto profondamente.

Sicuramente è possibile, giusto? In tal caso, qual è la migliore pratica?

AGGIORNAMENTO: Ecco un altro tentativo di spiegarlo. Voglio che l'utente sia in grado di compilare un ordine (manifest) per un ottimizzatore personalizzato, qualcosa come ordinare da un menu cinese - uno dalla colonna A, uno dalla colonna B, ecc. Cameriere, dalla colonna A (updaters) , Avrò l'aggiornamento BFGS con salsa Cholesky-decompositon. Dalla colonna B (line-searchers), avrò la ricerca della linea di interpolazione cubica con un eta di 0.4 e un rho di 1e-4, per favore. Etc ...

AGGIORNAMENTO: Va bene, va bene. Ecco il gioco che ho fatto. Lo offro a malincuore, perché sospetto sia un approccio completamente sbagliato. Funziona bene sotto vC++ 2008.

#include <boost/utility.hpp> 
#include <boost/type_traits/integral_constant.hpp> 

namespace dj { 

struct CBFGS { 
    void bar() {printf("CBFGS::bar %d\n", data);} 
    CBFGS(): data(1234){} 
    int data; 
}; 

template<class T> 
struct is_CBFGS: boost::false_type{}; 

template<> 
struct is_CBFGS<CBFGS>: boost::true_type{}; 

struct LMQN {LMQN(): data(54.321){} 
    void bar() {printf("LMQN::bar %lf\n", data);} 
    double data; 
}; 

template<class T> 
struct is_LMQN: boost::false_type{}; 

template<> 
struct is_LMQN<LMQN> : boost::true_type{}; 

// "Order form" 
struct default_optimizer_traits { 
    typedef CBFGS update_type; // Selection from column A - updaters 
}; 

template<class traits> class Optimizer; 

template<class traits> 
void foo(typename boost::enable_if<is_LMQN<typename traits::update_type>, 
     Optimizer<traits> >::type& self) 
{ 
    printf(" LMQN %lf\n", self.data); 
} 

template<class traits> 
void foo(typename boost::enable_if<is_CBFGS<typename traits::update_type>, 
     Optimizer<traits> >::type& self) 
{ 
    printf("CBFGS %d\n", self.data); 
} 

template<class traits = default_optimizer_traits> 
class Optimizer{ 
    friend typename traits::update_type; 
    //friend void dj::foo<traits>(typename Optimizer<traits> & self); // How? 
public: 
    //void foo(void); // How??? 
    void foo() { 
     dj::foo<traits>(*this); 
    } 
    void bar() { 
     data.bar(); 
    } 
//protected: // How? 
    typedef typename traits::update_type update_type; 
    update_type data; 
}; 

} // namespace dj 



int main() { 
    dj::Optimizer<> opt; 
    opt.foo(); 
    opt.bar(); 
    std::getchar(); 
    return 0; 
} 
+2

Puoi mostrare un esempio di cosa stai facendo ora in pseudo codice, forse? –

+0

@Chris Kaminski: Il codice di 10 anni o gli esperimenti? Quest'ultimo, presumo. –

+0

Forse entrambi? Voglio dire, ci sono molti esempi nel STL e aumentano l'uso delle funzioni che operano su collezioni, ad esempio mappa/riduci. –

risposta

1

Penso che la specializzazione dei modelli sia un passo nella giusta direzione.Questo non funziona con le funzioni, quindi sono passato alle classi. L'ho modificato in modo da modificare i dati. Non sono così venduto ai membri protetti e faccio amicizia. I membri protetti senza ereditarietà sono un odore. Rendilo pubblico o forniscici e rendilo privato.

template <typename> 
struct foo; 

template <> 
struct foo<LMQN> 
{ 
    template <typename OptimizerType> 
    void func(OptimizerType& that) 
    { 
     printf(" LMQN %lf\n", that.data.data); 
     that.data.data = 3.14; 
    } 
}; 

template <> 
struct foo<CBFGS> 
{ 
    template <typename OptimizerType> 
    void func(OptimizerType& that) 
    { 
     printf(" CBFGS %lf\n", that.data.data); 
    } 
}; 

template<class traits = default_optimizer_traits> 
class Optimizer{ 
public: 
    typedef typename traits::update_type update_type; 
    void foo() { 
     dj::foo<typename traits::update_type>().func(*this); 
    } 
    void bar() { 
     data.bar(); 
    } 
    update_type data; 
}; 
+0

Non è possibile per l'updater foo aggiornare i membri protetti di Optimizer di classe o leggere il suo stato. La funzione da istanziare dovrebbe essere un membro di Optimizer di classe o, in mancanza, un amico che ottiene come argomento il puntatore di questo dello Strumento di ottimizzazione. Guarda la prova che ho fatto. Le funzioni funzionano, ma solo se pubblico tutto in pubblico come strumento di ottimizzazione, perché non ho capito come dichiarare le funzioni come amici. –

+0

(Commentando nuovamente perché Stackoverflow non mi consente di modificare quello precedente.) Penso che tu sia sulla strada giusta con specializzazione esplicita. Ma questo esempio manca il punto. Non è possibile per l'updater foo aggiornare i membri protetti di Optimizer di classe o leggere il suo stato. [... come prima]. –

+0

Sembra che questo approccio sia un vicolo cieco. Nella risposta accettata qui, http://stackoverflow.com/questions/2097811/c-syntax-for-explicit-specialization-of-a-template-function-in-a-template-class dice "Puoi 'specializzare una funzione membro senza specializzarsi esplicitamente nella classe contenente. " Quindi entriamo nel problema di "invio multiplo" con il menu cinese. Ma c'è speranza. Continua dicendo: "Ciò che puoi fare è inoltrare le chiamate a una funzione membro di un tipo parzialmente specializzato." Lo esaminerò. (#define, # ifdef e le macro sono sempre migliori. :-)) –

0

L'utilizzo di enable_if è un po 'strano. L'ho visto usato solo 2 modi:

  • al posto del tipo di ritorno
  • come parametro supplementare (default)

usato per un parametro reale potrebbe causare il caos.

In ogni caso, è sicuramente possibile usarlo per le funzioni di membro:

template<class traits = default_optimizer_traits> 
class Optimizer{ 
    typedef typename traits::update_type update_type; 
public: 

    typename boost::enable_if< is_LQMN<update_type> >::type 
    foo() 
    { 
    // printf is unsafe, prefer the C++ way ;) 
    std::cout << "LQMN: " << data << std::endl; 
    } 

    typename boost::enable_if< is_CBFGS<update_type> >::type 
    foo() 
    { 
    std::cout << "CBFGS: " << data << std::endl; 
    } 


private: 
    update_type data; 
}; 

Si noti che per impostazione predefinita enable_if rendimenti void, che è eminentemente adatto come tipo di ritorno nella maggior parte dei casi. La sintassi del "parametro" è normalmente riservata ai casi del costruttore, perché non hai un tipo di reso a tua disposizione, ma in generale preferisci usare il tipo restituito in modo che non interferisca con la risoluzione di sovraccarico.

EDIT:

La soluzione precedente non funziona, come indicato nei commenti. Non riuscivo a trovare qualsiasi alternativa usando enable_if, solo il modo sovraccarico "semplice":

namespace detail 
{ 
    void foo_impl(const LMQN& data) 
    { 
    std::cout << "LMQN: " << data.data << std::endl; 
    } 

    void foo_impl(const CBFGS& data) 
    { 
    std::cout << "CBFGS: " << data.data << std::endl; 
    } 
} // namespace detail 

template<class traits = default_optimizer_traits> 
class Optimizer{ 
    typedef typename traits::update_type update_type; 

public: 
    void foo() { detail::foo_impl(data); } 

private: 
    update_type data; 
}; 

Non è enable_if ma fa il lavoro senza esporre Optimizer interni a tutti. BACIO?

+1

Sei sicuro che il codice funzioni? Da codice simile sto ricevendo errori reali, non errori di sostituzione (che non sono errori solo nei modelli di funzione). – UncleBens

+0

Penso di capire: non stiamo testando i parametri del template propri dei metodi, ma invece il parametro template è fisso mentre istanziamo la classe che giocherà il caos con SFINAE, immagino. Dovrò cercare di trovare un'alternativa migliore allora. Questo è quello che succede quando non ti compilo, immagino. –

+0

Haha, il tempo è passato amico! Devi ancora aggiustarlo xD –

2

Una soluzione semplice potrebbe essere quella di utilizzare semplicemente l'inoltro basato su tag, ad es. qualcosa come questo:

template<class traits> 
void foo(Optimizer<traits>& self, const LMQN&) { 
    printf(" LMQN %lf\n", self.data.data); 
} 

template<class traits> 
void foo(Optimizer<traits>& self, const CBFGS&) { 
    printf("CBFGS %d\n", self.data.data); 
} 

template<class traits = default_optimizer_traits> 
class Optimizer { 
    friend class traits::update_type; 
    friend void dj::foo<traits>(Optimizer<traits>& self, 
          const typename traits::update_type&); 
public: 
    void foo() { 
     dj::foo<traits>(*this, typename traits::update_type()); 
    } 
    void bar() { 
     data.bar(); 
    } 
protected: 
    typedef typename traits::update_type update_type; 
    update_type data; 
}; 

Oppure, se volete a diverse funzioni comodamente gruppo insieme per diversi tratti, forse qualcosa come questo:

template<class traits, class updater=typename traits::update_type> 
struct OptimizerImpl; 

template<class traits> 
struct OptimizerImpl<traits, LMQN> { 
    static void foo(Optimizer<traits>& self) { 
     printf(" LMQN %lf\n", self.data.data); 
    } 
}; 

template<class traits> 
struct OptimizerImpl<traits, CBFGS> { 
    static void foo(Optimizer<traits>& self) { 
     printf("CBFGS %d\n", self.data.data); 
    } 
}; 

template<class traits = default_optimizer_traits> 
class Optimizer{ 
    friend class traits::update_type; 
    friend struct OptimizerImpl<traits>; 
public: 
    void foo() { 
     OptimizerImpl<traits>::foo(*this); 
    } 
    // ... 
}; 
+0

Questo è quello che stavo per scrivere in qualche modo. Mi sono concentrato sull'uso di 'enable_if' quando il problema non lo richiede di certo.Un semplice schema 'Strategy' o' Policy'-based sembra molto più adatto. –

+0

Sono sulla stessa pista. L'idea mi è venuta mentre dormivo. Mi succede molto. La mia idea è, dal momento che le funzioni membro non possono essere specializzate a meno che la classe contenente non sia specificamente specializzata, avrò un modello che divide il manifest nelle sue parti componenti e invoca un altro modello che le separa come quello che mostri. Ho cani e l'IRS per fare i conti oggi. Ma ci penserò io. –

+0

@ Jive: facci sapere come va. Da quello che hai mostrato, non vedo la necessità di inserire un altro livello di template - ma allora potresti avere qualcos'altro in mente. –

1

sarebbe banale per definire i tratti con # define e il membro funziona con # ifdef e macro. Ma è così vent'anni fa.

Sebbene possa valere la pena di imparare nuovi metodi, i macro sono spesso il modo più semplice di fare le cose e non dovrebbero essere scartati come strumento solo perché sono "vecchi". Se guardi l'MPL in boost e il libro su TMP troverai molto uso del preprocessore.

+0

Sto solo pensando che sia un enigma a questo punto. Sono decisamente vecchia scuola. Non ho alcuna reazione allergica alle macro pre-processore. Ironia della sorte, ho fatto una campagna per alcune delle caratteristiche del modello con cui sto lottando. Ciò non ha avuto alcun effetto però. Bjarne si è detto d'accordo sui lineamenti, ma a quel punto ha detto di averlo consegnato al comitato e di esserne rimasto praticamente fuori. –

0

Ecco cosa ho trovato (l'OP). Puoi renderlo più bello?

La classe del modello di ottimizzazione principale eredita le classi di implementazione della politica. Fornisce a tali classi l'accesso ai membri protetti di Optimizer di cui hanno bisogno. Un'altra classe di template Optimizer divide il manifest nelle sue parti costitutive e crea un'istanza del modello principale di Optimizer.

#include <iostream> 
#include <cstdio> 

using std::cout; 
using std::endl; 

namespace dj { 

// An updater. 
struct CBFGS { 
    CBFGS(int &protect_) 
     : protect(protect_) 
    {} 

    void update() { 
     cout << "CBFGS " << protect << endl; 
    } 

    // Peek at optimizer's protected data 
    int &protect; 

}; 

// Another updater 
struct LMQN { 
    LMQN(int &protect_) 
     : protect(protect_) 
    {} 

    void update() { 
     cout << "LMQN " << protect << endl; 
    } 

    // Peek at optimizer's protected data 
    int &protect; 

}; 

// A line-searcher 
struct cubic_line_search { 
    cubic_line_search (int &protect2_) 
     : protect2(protect2_) 
    {} 

    void line_search() { 
     cout << "cubic_line_search " << protect2 << endl; 
    } 

    // Peek at optimizer's protected data 
    int &protect2; 

}; 

struct default_search_policies { 
    typedef CBFGS update_type; 
    typedef cubic_line_search line_search_type; 
}; 

template<class Update, class LineSearch> 
class Opt_base: Update, LineSearch 
{ 
public: 
    Opt_base() 
     : protect(987654321) 
     , protect2(123456789) 
     , Update(protect) 
     , LineSearch(protect2) 
    {} 
    void minimize() { 
     update(); 
     line_search(); 
    } 

protected: 
    int protect; 
    int protect2; 
}; 

template<class Search_Policies=default_search_policies> 
class Optimizer: 
    public Opt_base<typename Search_Policies::update_type 
        , typename Search_Policies::line_search_type 
        > 
{}; 

} // namespace dj 



int main() { 
    dj::Optimizer<> opt; // Use default search policies 
    opt.minimize(); 

    struct my_search_policies { 
     typedef dj::LMQN update_type; 
     typedef dj::cubic_line_search line_search_type; 
    }; 

    dj::Optimizer<my_search_policies> opt2; 
    opt2.minimize(); 

    std::getchar(); 
    return 0; 
} 
Problemi correlati