2016-10-09 35 views
7

Często mam pewne zachowanie prototypowe, które generuje dane wyjściowe na podstawie niektórych metod projektowania. Modyfikuję metodę projektowania, która pozwala na wiele funkcji, których potrzebuję. Czasami jednak metoda projektowania jest podawana w czasie wykonywania, więc zazwyczaj muszę napisać wielką instrukcję przełączania. Zwykle wygląda to tak:Wykonanie funkcji opartej na liczbie całkowitej na podstawie parametru wykonawczego

enum class Operation 
{ 
    A, B 
}; 


template<Operation O> 
    void execute(); 

template<> 
    void execute<A>() 
    { 
     // ... 
    } 

template<> 
    void execute<B>() 
    { 
     // ... 
    } 

void execute(Operation o) 
{ 
    switch (o) 
    { 
    case Operation::A: return execute<Operation::A>(); 
    case Operation::B: return execute<Operation::B>(); 
    } 
} 

Jestem ciekaw, czy ktoś zorientowali się piękny wzór dla tego systemu - główne wady tej metody jest to, że trzeba wpisać wszystkie obsługiwane wyliczeń i zrobić utrzymywać kilka miejsc, jeśli są realizowane nowe wyliczenia.

e: Powinienem dodać, że przyczyną problemów z szablonami kompilacji jest umożliwienie kompilatorowi inline metod w HPC, a także dziedziczenie właściwości constexpr.

e2: w rzeczywistości, domyślam się, że proszę o to, aby kompilator wygenerował wszystkie możliwe ścieżki kodu przy użyciu niejawnej struktury przełącznika. Być może jakiś rekurencyjny szablon magii?

+0

Co z wykorzystaniem dziedziczenia i polimorfizmu? –

+0

Jak już dodałem, niezwykle ważne jest, aby kompilator mógł wykonywać inlineing i optymalizacje w czasie kompilacji (tj. Cała struktura kodu jest widoczna i deterministyczna). W przeciwnym razie, wirtualne funkcje oczywiście rozwiązują problem. – Shaggi

Odpowiedz

4

Jeśli naprawdę chcesz wykorzystać szablony do tego zadania, możesz użyć techniki podobnej do techniki this one.

// Here second template argument default to the first enum value 
template<Operation o, Operation currentOp = Operation::A> 
// We use SFINAE here. If o is not equal to currentOp compiler will ignore this function. 
auto execute() -> std::enable_if<o == currentOp, void>::type 
{ 
    execute<currentOp>(); 
} 

// Again, SFINAE technique. Compiler will stop search if the template above has been instantiated and will ignore this one. But in other case this template will be used and it will try to call next handler. 
template<Operation o, Operation currentOp = Operation::A> 
void execute() 
{ 
    return execute<o, static_cast<Operation>(static_cast<int>(currentOp) + 1)(c); 
} 
+0

Interesujące (link jest uszkodzony btw), ja to sprawdzę – Shaggi

+0

Naprawiłem link. –

2
template<class F, std::size_t...Is> 
void magic_switch(std::size_t N, F&& f, std::index_sequence<Is...>){ 
    auto* pf = std::addressof(f); 
    using pF=decltype(pf); 
    using table_ptr = void(*)(pF); 
    static const table_ptr table[]={ 
    [](pF){ std::forward<F>(*pf)(std::integral_constant<std::size_t, Is>{}); }... 
    }; 
    return table[N](pf); 
} 
template<std::size_t Count, class F> 
void magic_switch(std::size_t N, F&& f){ 
    return magic_switch(N, std::forward<F>(f), std::make_index_sequence<Count>{}); 
} 

To sprawia, że ​​stół skok, który wywołuje lambda w czasie kompilacji stałą, zbierając którego wejście oparte na stałej wykonawczego. Co jest bardzo podobne do tego, w jaki sposób czasami kompilowane jest zestawienie instrukcji switch.

void execute(Operation o) { 
    magic_switch<2>(std::size_t(o), [](auto I){ 
    execute<Operation(I)>(); 
    }); 
} 

Modyfikowanie go w celu powrotu do stanu bezwolnego jest możliwe, ale wszystkie gałęzie muszą zwracać ten sam typ.