5

Angenommen, wir haben Funktion wieCode-Duplizierung Vermeidung für Runtime-to-Compiler-numerischen Parameter Übersetzung

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

und der Einfachheit halber davon aus, dass wir wissen nur, dass (konstant) Werte N_1, N_2 ... N_k gelten für N.

Nun, ich nehme an, dass ein Compiler-Parameter eine Laufzeit machen wollen, mit foo() als Black-Box, das heißt implementieren:

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

von foo<,>() Anrufe. Wie soll ich das machen? Offensichtlich kann ich schreiben:

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

... aber das macht mich alles schmutzig fühlen. Ich könnte ein MAP() -Meta-Makro verwenden, um diese k Zeilen zu erzeugen, nehme ich an; Aber kann ich etwas Besseres und weniger Makronesisches tun, um dasselbe zu erreichen? Ist es möglich, etwas wie das oben genannte zu schreiben, das general ist, und funktioniert für jede variable Vorlage und eine feste Folge von konstanten Werten?

Hinweise:

  • C++ 11/14/17-spezifische Vorschläge sind natürlich willkommen.
  • Die N's sind nicht notwendigerweise zusammenhängend, noch klein, noch sortiert. z.B. nehme N_2 = 123456789 und N_5 = 1.

Antwort

6

Sie einen Funktionszeiger Tabelle machen könnte:

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)]; 

Und dann rufen Sie einfach die, die Sie wollen:

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](); 
} 

Wenn die N_k s sind nicht zusammenhängend, dann können wir ein Lambda für Inline-Parameter Entpacken verwenden:

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

Wenn die oben zu langsam ist, dann denke ich nur manuell einen std::unordered_map<unsigned, void(*)()> oder etwas bauen.

+0

@einpoklum Oh sind die ' N_k's nicht zusammenhängend? – Barry

+0

Nein, ich habe nie angedeutet, dass sie es waren. – einpoklum

+0

Eigentlich sollte Ihr Indexer wahrscheinlich schneller als eine ungeordnete Karte sein, wenn k groß ist (vorausgesetzt, der Compiler ist schlau genug). +1. – einpoklum

3

In dieser Art von Situationen möchte ich eine statische Tabelle von Funktionszeigern erstellen, mit einem dynamischen Parameter, der entscheidet, an welchen zu senden ist. Unten ist eine Implementierung, die dies erreicht, in der Funktion foo_dynamic. Zu dieser Funktion geben Sie den maximalen Wert von N an, den Sie unterstützen möchten, und es erstellt eine statische Tabelle von Funktionszeigern, die einige rekursive Vorlagen verwenden. Dann dereferenzieren Sie sich mit Ihrem dynamischen Parameter in diese Tabelle.

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: die Einschränkungen in der Frage bearbeiten Gegeben, hier ist ein Ansatz, bei dem gültige Indizes manuell angegeben werden, die eine unordered_map anstelle eines Arrays verwendet:

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

Angenommen, N_k = 10000000. – einpoklum

+0

Ja, funktioniert auch dafür (mit der entsprechenden '-template-depth = 10000000' Schalter), obwohl kompiliert * sehr * langsam – Smeeheey

+0

Siehe bearbeiten nach Ihrer Bearbeitung – Smeeheey