2010-04-14 12 views
6

Nie chcę powiedzieć, że nie mogę tego zrozumieć, ale nie mogę tego zrozumieć. Przeszukałem go i przeszukano Stack Overflow, a potem wyszedłem pusty.Funkcje członków klasy tworzone za pomocą cech [polityki, faktycznie]

Streszczenie i prawdopodobnie zbyt ogólnikowa forma pytania brzmi: Jak mogę użyć wzorca znaków do utworzenia instancji funkcji?[Aktualizacja: użyłem tutaj niewłaściwego terminu. Powinien to być "polityka", a nie "cechy". Cechy opisują istniejące klasy. Zasady określają klasy syntetyczne.] Pojawiło się pytanie podczas modernizacji zestawu optymalizatorów funkcji wielowymiarowych, które napisałem ponad 10 lat temu.

Wszystkie optymalizatory działają, wybierając prostą ścieżkę w przestrzeni parametrów z dala od bieżącego najlepszego punktu ("aktualizacja"), a następnie znajdując lepszy punkt na tej linii ("wyszukiwanie linii"), a następnie testując dla warunku "done", a jeśli nie, to iterowanie.

Istnieją różne metody przeprowadzania aktualizacji, wyszukiwania linii i możliwe do zrobienia testu i inne rzeczy. Mieszać i łączyć. Różne formuły aktualizacji wymagają różnych danych zmiennych stanu. Na przykład aktualizacja LMQN wymaga wektora, a aktualizacja BFGS wymaga macierzy. Jeśli ocenianie gradientów jest tanie, wyszukiwanie linii powinno to zrobić. Jeśli nie, powinien korzystać tylko z ocen funkcji. Niektóre metody wymagają dokładniejszych wyszukiwań linii niż inne. To tylko kilka przykładów.

Oryginalna wersja tworzy kilka kombinacji za pomocą funkcji wirtualnych. Niektóre cechy są wybierane przez ustawienie bitów trybu testowanych w czasie wykonywania. Fuj. Byłoby trywialne definiowanie cech za pomocą # define i funkcji składowych za pomocą # ifdef i makr. Ale tak było dwadzieścia lat temu. Wpadło mi w głowę, że nie potrafię wymyślić nowoczesnego sposobu bzika.

Gdyby była tylko jedna cecha, która byłaby różna, mógłbym użyć ciekawego powtarzającego się wzorca szablonu . Ale nie widzę sposobu, aby rozszerzyć to na dowolne kombinacje cech.

Próbowałem to zrobić przy użyciu boost::enable_if itp. Wyspecjalizowane informacje o stanie były łatwe. Udało mi się wykonać funkcje, ale tylko poprzez użycie zewnętrznych funkcji, które nie są przyjaciółmi, które mają parametr this -pointer. Nigdy nawet nie doszedłem do tego, jak sprawić, by funkcje były przyjacielskie, a tym bardziej funkcje członków. Kompilator (VC++ 2008) zawsze skarżył się, że rzeczy nie pasują. Krzyczałem: "SFINAE, ty głupku!" ale kretynem jest prawdopodobnie ja.

Być może kluczowa jest wysyłka tagów. Nie wpadłem w to bardzo głęboko.

Z pewnością jest to możliwe, prawda? Jeśli tak, jaka jest najlepsza praktyka?

AKTUALIZACJA: Oto kolejna próba wyjaśnienia tego. Chcę, aby użytkownik mógł wypełnić zlecenie (manifest) niestandardowego optymalizatora, coś w rodzaju zamawiania z chińskiego menu - jednego z kolumny A, jednego z kolumny B, itp. Kelner, z kolumny A (aktualizatory) , Poproszę aktualizację BFGS o sos z dekompozycji Cholesky'ego. Z kolumny B (szukacze lini), będę mieć interpolację sześciennych linii wyszukiwania z eta 0,4 i rho z 1e-4, proszę. Itd ...

AKTUALIZACJA: OK, okej. Oto gra, którą wykonałem. Oferuję to niechętnie, ponieważ podejrzewam, że to podejście całkowicie błędne. Działa poprawnie pod 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

Czy możesz pokazać przykład tego, co robisz teraz w pseudo-kodzie? –

+0

@Chris Kaminski: 10-letni kod lub eksperymenty? Te ostatnie, jak sądzę. –

+0

Oba być może? Mam na myśli wiele przykładów w STL i zwiększenie wykorzystania funkcji działających na kolekcjach, np. Map/reduce. –

Odpowiedz

1

Myślę, że specjalizacja szablonu jest krokiem we właściwym kierunku.To nie działa z funkcjami, więc przełączyłem się na klasy. Zmieniłem go, modyfikując dane. Nie jestem tak sprzedawany na chronionych członkach i nawiązywaniu przyjaźni. Członkowie chronieni bez dziedziczenia są zapachem. Upublicznij lub zapewnij dostęp i uczyń go prywatnym.

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

Nie ma sposobu, aby aktualizator foo zaktualizował chronione elementy klasy Optimizer lub odczytał jego stan. Funkcja, która ma być utworzona w instancji, powinna należeć do klasy Optimizer, a jeśli nie, przyjaciela, który jako argument przekazuje tę wskazówkę Optymalizatora. Spójrz na próbę, którą zrobiłem. Funkcje działają, ale tylko wtedy, gdy zrobię wszystko w klasie Optymalizator publiczny, ponieważ nie wiem, jak sprawić, by funkcje były przyjaciółmi. –

+0

(Komentując ponownie, ponieważ Stackoverflow nie pozwala mi edytować poprzedniego.) Myślę, że jesteś na dobrej drodze z wyraźną specjalizacją. Ale ten przykład pomija punkt. Aktualizator foo nie może zaktualizować chronionych elementów klasy Optimizer ani odczytać jego stanu. [... jak wcześniej]. –

+0

Wygląda na to, że to podejście jest ślepym zaułkiem. W zaakceptowanej odpowiedzi tutaj: http://stackoverflow.com/questions/2097811/c-syntax-for-explicit-specialization-oftemplate-function-in-a-topplate-class mówi: "Możesz nie specjalizuje funkcji składowej bez jawnej specjalizacji klasy zawierającej. " Wchodzimy więc w problem "wielokrotnej wysyłki" z chińskim menu. Ale jest nadzieja. Dalej mówi się: "Możliwe jest jednak przekazywanie wywołań do funkcji składowej typu częściowo wyspecjalizowanego". Będę się nad tym zastanawiał. (#defines, # ifdef i makra wyglądają coraz lepiej. :-)) –

0

Twoje użycie enable_if jest nieco dziwne. Widziałem kiedyś to tylko 2 sposoby:

  • w miejscu zwracanego typu
  • jako parametr uzupełniający (niespłaconych)

używanie go do rzeczywistego parametru może spowodować spustoszenie.

W każdym razie, na pewno można użyć go do funkcji składowych:

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; 
}; 

Zauważ, że domyślnie enable_if zwrotów void, która doskonale nadaje się jako typ zwracany w większości przypadków. Składnia "parametr" jest zwykle zarezerwowana dla przypadków konstruktora, ponieważ nie masz wtedy do dyspozycji typu zwracanego, ale ogólnie wolisz używać typu zwracanego, aby nie mieszał się z rozdzielczością przeciążenia.

EDIT:

Dotychczasowe rozwiązanie nie działa, jak wspomniano w komentarzach. Nie mogłem znaleźć żadnej alternatywy korzystając enable_if, tylko „prosty” sposób przeciążeniem:

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; 
}; 

To nie enable_if ale spełnia swoje zadanie bez narażania Optimizer wewnętrzne do każdego. POCAŁUNEK ?

+1

Czy jesteś pewien, że kod działa? Z podobnego kodu otrzymuję prawdziwe błędy, a nie awarie podmiany (które nie są błędami tylko w szablonach funkcji). – UncleBens

+0

Sądzę, że rozumiem: nie testujemy parametrów szablonu właściwych dla metod, ale zamiast tego parametr szablonu jest ustalany, gdy tworzymy instancję klasy, która będzie spustoszyć w SFINAE. Będę musiał spróbować wymyślić lepszą alternatywę. Tak się dzieje, gdy nie kompilujesz, tak myślę. –

+0

Haha, czas minął mate! Nadal musisz to naprawić xD –

2

Prostym rozwiązaniem może być po prostu stosowanie przekierowania opartego na znacznikach, np. coś tak:

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; 
}; 

Albo jeśli chcesz wygodnie grupowych kilka funkcji razem z różnymi cechami, może coś tak:

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

To właśnie miałem zamiar napisać jakoś. Skoncentrowałem się na użyciu 'enable_if', gdy problem z pewnością na pewno go nie wymaga.Prosty model "Strategiczny" lub "oparty na zasadach" wydaje się bardziej odpowiedni. –

+0

Jestem na tym samym torze. Pomysł przyszedł mi do snu. To dużo mi się przydarza. Moim pomysłem jest, ponieważ funkcje członkowskie nie mogą być wyspecjalizowane, chyba że klasa zawierająca jest wyspecjalizowana, będę miał szablon, który podzieli manifest na jego części składowe, a następnie wywoła inny szablon, który je oddzieli tak jak to, co pokazujesz. Mam dzisiaj psy i IRS, żeby się zmiękczyć. Ale się do tego zabiorę. –

+0

@Jive: Daj nam znać, jak to działa. Z tego, co pokazałeś, nie widzę potrzeby wstawiania kolejnej warstwy szablonu - ale możesz mieć na myśli coś innego. –

1

byłoby trywialne do określenia cech z funkcjami # define i member z # ifdef i makrami. Ale tak było dwadzieścia lat temu.

Chociaż może warto poznać nowe metody, makra są często najprostszym sposobem robienia rzeczy i nie powinny być odrzucane jako narzędzie tylko dlatego, że są "stare". Jeśli spojrzysz na MPL w doładowaniu i książkę na temat TMP, wiele z tego skorzystasz z preprocesora.

+0

Po prostu uważam, że jest to zagadka w tym momencie. Jestem zdecydowanie old school. Nie mam reakcji alergicznej na makra preprocesora. Jak na ironię, prowadziłem kampanię na temat niektórych funkcji szablonu, z którymi walczę. To jednak nie miało żadnego skutku. Bjarne zgodził się co do cech, ale powiedział, że w tym momencie przekazał go komisji i prawie się z tym nie zgadzał. –

0

Oto, co wymyśliłem (OP). Czy możesz sprawić, że będzie chłodniej?

Główna klasa szablonu Optymalizatora dziedziczy klasy implementacji polityk. Zapewnia tym klasom dostęp do chronionych członków Optymalizatora, których potrzebują. Inna klasa szablonu Optymalizatora dzieli manifest na części składowe i tworzy główny szablon Optymalizatora.

#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; 
}