C++ template instantiation: Avoiding long switches

前端 未结 10 1138
轻奢々
轻奢々 2020-12-08 21:33

I have a class depending on an integer template parameter. At one point in my program I want to use one instantiation of this template, depending on a value of this paramet

相关标签:
10条回答
  • 2020-12-08 21:50

    You could use a variadic template, maybe like this:

    #include <cstdlib>
    #include <string>
    
    int main(int argc, char * argv[])
    {
        if (argc != 2) { return EXIT_FAILURE; }
    
        handle_cases<1, 3, 4, 9, 11>(std::stoi(argv[1]));
    }
    

    Implementation:

    template <int ...> struct IntList {};
    
    void handle_cases(int, IntList<>) { /* "default case" */ }
    
    template <int I, int ...N> void handle_cases(int i, IntList<I, N...>)
    {
        if (I != i) { return handle_cases(i, IntList<N...>()); }
    
        Wrapper<I> w;
        w.foo();
    }
    
    template <int ...N> void handle_cases(int i)
    {
        handle_cases(i, IntList<N...>());
    }
    
    0 讨论(0)
  • 2020-12-08 21:50

    Here's another approach:

    template<int N>
    void do_foo()
    {
        Wrapper<N> w;
        w.foo();
    }
    
    template<int N, int... Ns>
    struct fn_table : fn_table<N - 1, N - 1, Ns...>
    {
    };
    
    template<int... Ns>
    struct fn_table<0, Ns...>
    {
        static constexpr void (*fns[])() = {do_foo<Ns>...};
    };
    
    template<int... Ns>
    constexpr void (*fn_table<0, Ns...>::fns[sizeof...(Ns)])();
    
    int main(int argc, char *argv[])
    {
        std::string arg = argv[1];
        int arg_int = std::stoi(arg);
    
        // 4 if you have Wrapper<0> to Wrapper<3>.
        fn_table<4>::fns[arg_int]();
    }
    
    0 讨论(0)
  • 2020-12-08 21:53

    As an general alternative to switches, you could use a vector or map of function pointers to remove the switch:

    template <int i>
    int foo()
    {
        Wrapper<i> w;
        w.foo();
        return i;
    }
    
    static std::vector<int(*)()> m;
    
    void init()
    {
        m.push_back(&foo<0>);
        m.push_back(&foo<1>);
    }
    
    void bar(int i)
    {
        m[i]();
    }
    

    In C++11 you could use an initializer list to initialize the vector or map.

    0 讨论(0)
  • 2020-12-08 21:54

    arg_int is a runtime parameter so there is no way to attach it directly to a template parameter. You could use some kind of handler table which would remove the switch statement here.

    You'd use something like lookup_handler( int N ) returning a type handler which might be a lambda invoking one of those template functions.

    Registering all your lambdas on the table could be done recursively starting with the highest numbered one you allow.

    template< unsigned N > register_lambda()
    {
         table.add( Wrapper<N>() );
         register_lambda< N-1 >;
    }
    

    and specialise for register_lambda<0>

    Then somewhere you call register_lambda<32> say and you have registered all the numbers from 0 to 32.

    One way to implement such a table is:

    class lambda_table
    {
     typedef std::function<void()> lambda_type; 
        public:
            void add( lambda_type );
            bool lookup( size_t key, lambda_type & lambda ) const;
    };
    

    From main() or wherever you want to invoke it you have a reference to this table (call it table) then call

    lambda_type lambda;
    if( table.find( arg_int, lambda ) )
            lanbda();
    else
          default_handler();
    

    You might change this to give the table itself a default handler where none has been supplied for this number.

    Although lambdas can wrap all kinds of data members you might actually want your templates to be classes in a hierarchy rather than lambdas given the data storage within them.

    0 讨论(0)
  • 2020-12-08 21:56

    To explain @Simple's solution that is based on a static function table:

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    template<int N>
    void do_foo()
    {
        cout << N << endl;
    }
    
    template<int N, int... Ns>
    struct fn_table : fn_table<N - 1, N - 1, Ns...> {
    };
    
    template<int... Ns>
    void p()
    {
        int a[] = {Ns...};
        for (int i = 0; i < sizeof(a)/sizeof(int); ++i) 
            cout << a[i] << endl;
    }
    
    // Recursion-base instantiation with leading 0 parameter.
    template<int... Ns>
    struct fn_table<0, Ns...> {
        // calling fn_table<4> would call recursively with template parameters: <4>, <3, 3>, <2, 2, 3>, <1, 1, 2, 3>, <0, 0, 1, 2, 3>. The last call would create 4 (we expand Ns without the first 0) do_foo functions using a variadic parameter pack "...".
        static constexpr void (*fns[])() = {
            p<Ns...> // call a function that prints Ns... for illustration, expanding the parameters inside p instead of duplicating it.
            //do_foo<Ns>...
        };
    };
    
    template<int... Ns>
    constexpr void (*fn_table<0, Ns...>::fns[sizeof...(Ns)])();
    
    int main(int argc, char *argv[])
    {
        int arg_int = 0;
    
        // 4 if you have Wrapper<0> to Wrapper<3>.
        fn_table<4>::fns[arg_int]();
    }
    
    0 讨论(0)
  • 2020-12-08 21:56

    Building the table using an integer_sequence. I also added: i) a sequence start, ii) a parameter for the type of the function, e.g. to receive and return values.

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    struct Foo {
        template<int N>
        static void foo(int &a) {
            cout << N << endl;
            a = N + 1;
        }
    };
    
    template<int start, typename F, typename R, typename T, T... ints>
    auto fn_table_( integer_sequence<T, ints...> int_seq )
    {
        vector<R> expand = { F::foo<ints+start>... };
        vector<R> dummy( start );
        expand.insert( expand.begin(), dummy.begin(), dummy.end() );
    
        return expand;
    }
    
    template<int start, typename F, typename R, int N>
    auto fn_table()
    {
        return fn_table_<start, F, R>( make_integer_sequence<int, N-start>{} );
    }
    
    void main()
    {
        int arg_int = 5;
    
        typedef void (*fun_type)( int & );
        auto fns = fn_table<4, Foo, fun_type, 7>();
        int a;
        fns[arg_int]( a );
        cout << a << endl;
        cout << "all:\n";
        for (int i = 0; i < fns.size() ; ++i) 
            if ( fns[i] )
                fns[i]( a );
    }
    
    0 讨论(0)
提交回复
热议问题