5

Załóżmy, że mamy takie funkcje jakUnikanie powielania kodu do runtime-do-czasu kompilacji tłumaczenia parametr numeryczny

template <typename T, unsigned N> void foo(); 

i dla uproszczenia zakładamy, że wiemy, że tylko (stała) wartości N_1, N_2 ... N_k są ważne dla N.

Teraz załóżmy, że chcę, aby ten parametr kompilacji czas run-time jeden, używając foo() jako czarnej skrzynki, to znaczy realizować:

template <typename T> void foo(unsigned n); 

dokonując foo<,>() połączeń. Jak mam to zrobić? Oczywiście, mogę napisać:

template <typename T> void foo(unsigned n) { 
    switch(n) { 
    case N_1 : foo<T, N_1>(); break; 
    case N_2 : foo<T, N_2>(); break; 
    // etc. etc. 
    case N_k : foo<T, N_k>(); break; 
    } 
} 

... ale to sprawia, że ​​czuję się bardzo brudny. Mógłbym użyć meta-makro MAP() do wygenerowania tych k linii, jak przypuszczam; ale czy mogę zrobić cokolwiek lepiej i mniej makro, aby osiągnąć to samo? Czy można napisać coś podobnego do powyższego: ogólne i działa dla każdego szablonu variadic i ustalonej sekwencji wartości stałych?

Uwagi:

  • C++ 11/14/17-konkretne sugestie są oczywiście mile widziane.
  • N's niekoniecznie są ciągłe, ani małe, ani posortowane. na przykład Przypuszczam N_2 = 123456789 i N_5 = 1.

Odpowiedz

6

Można zrobić tabelę wskaźnik funkcji:

using F = void(*)(); 

template <class T, class > 
struct Table; 

template <class T, size_t... Is> 
struct Table<T, std::index_sequence<Is...> > { 
    static constexpr F fns[] = { 
     foo<T, Is>... 
    }; 
}; 

template <class T, size_t... Is> 
constexpr F Table<T, std::index_sequence<Is...> >::fns[sizeof...(Is)]; 

A potem po prostu powołać się na ten, który chcesz:

template <class T, size_t N> 
struct MakeTable : Table<T, std::make_index_sequence<N>> { }; 

template <typename T> 
void foo(unsigned n) { 
    MakeTable<T, MaxN>::fns[n](); 
} 

Jeśli N_k s nie sąsiadują ze sobą, wtedy możemy użyć lambda do rozpakowania parametrów w linii:

template <class T> 
void foo(unsigned n) { 
    using seq = std::index_sequence<N_1, N_2, ..., N_k>; 
    indexer(seq)([n](auto i){ 
     if (n == i) { 
      f<T, i>(); 
     } 
    }); 
} 

Jeśli powyższe jest zbyt powolny, to myślę, że po prostu ręcznie zbudować std::unordered_map<unsigned, void(*)()> lub coś.

+0

@einpoklum Oh są ' N_k's nie sąsiadują? – Barry

+0

Nie, nigdy nie sugerowałem, że są. – einpoklum

+0

W rzeczywistości indeksator powinien być prawdopodobnie szybszy niż mapa nieuporządkowana, chyba że k jest duże (zakładając, że kompilator jest wystarczająco inteligentny). +1. – einpoklum

3

W tego rodzaju sytuacjach lubię budować statyczną tabelę wskaźników funkcji, z dynamicznym parametrem decydującym o tym, do którego należy wysłać. Poniżej znajduje się implementacja, która to osiąga, w funkcji foo_dynamic. Do tej funkcji należy podać maksymalną wartość N, którą chcesz obsługiwać, i buduje statyczną tabelę wskaźników funkcji za pomocą niektórych szablonów rekursywnych. Następnie dereferencja do tej tabeli z parametrem dynamicznym.

using ftype = void (*)(); 

template <typename T, unsigned N> void foo() 
{ 
    std::cout << N << std::endl; 
} 

template <typename T, unsigned max> 
struct TablePopulator 
{ 
    static void populateFTable(ftype* table) 
    { 
     table[max] = foo<T,max>; 
     TablePopulator<T,max-1>::populateFTable(table); 
    } 
}; 

template <typename T> 
struct TablePopulator<T, 0> 
{ 
    static void populateFTable(ftype* table) 
    { 
     table[0] = foo<T,0>; 
    } 
}; 

template<typename T, unsigned max_N> 
std::array<ftype, max_N>& initTable() 
{ 
    static std::array<ftype, max_N> table; 
    TablePopulator<T, max_N-1>::populateFTable(table.data()); 
    return table; 
} 

template<typename T, unsigned max_N> 
void foo_dynamic(unsigned actualN) 
{ 
    static auto ftable = initTable<T, max_N>(); 
    if(actualN >= max_N) 
     throw std::runtime_error("Max param exceeded"); 

    ftable[actualN](); 
} 


int main() 
{ 
    foo_dynamic<int, 10>(1); 
    foo_dynamic<int, 10>(5); 

    return 0; 
} 

EDIT: Z uwagi na ograniczenia w edycji zapytania, tu jest podejście, gdzie ważne wskaźniki określone są ręcznie, co wykorzystuje unordered_map zamiast tablicy:

using ftype = void (*)(); 

template <typename T, unsigned N> void foo() 
{ 
    std::cout << N << std::endl; 
} 

template<typename T, size_t ... Indices> 
void foo_dynamic_indices(size_t actual_index) 
{ 
    static std::unordered_map<size_t, ftype> fmap = {{Indices, foo<T,Indices>}...}; 
    auto fIt = fmap.find(actual_index); 
    if(fIt == fmap.end()) 
     throw std::runtime_error("Index not found"); 

    fIt->second(); 
} 

int main() 
{ 

    foo_dynamic_indices<int, 0, 3, 400, 1021, 10000000>(10000000); 
    foo_dynamic_indices<int, 0, 3, 400, 1021, 10000000>(4); //Exception 

    return 0; 
} 
+0

Załóżmy, że N_k = 10000000. – einpoklum

+0

Tak, działa również na to (z odpowiednim przełącznikiem '-ftemplate-depth = 10000000'), chociaż kompiluje * bardzo * wolno – Smeeheey

+0

Zobacz edytuj po swojej edycji – Smeeheey