Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ template instantiation: Avoiding long switches

Tags:

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 parameter determined at runtime. Here is a simple example demonstrating how I would go about this currently, using a big switch statement:

#include <string>
#include <iostream>
#include <type_traits>

template<unsigned A>
struct Wrapper {
    typedef typename std::conditional<A==1, int, float>::type DataType;
    DataType content[A];
    void foo() {
        std::cout << A << std::endl;
    };
};    

int main(int argc, char *argv[])
{
    std::string arg = argv[1];
    int arg_int = std::stoi(arg);

    switch (arg_int) {
    case 1: {
        Wrapper<1> w;
        w.foo();
        break;
    }
    case 2: {
        Wrapper<2> w;
        w.foo();
        break;
    }
    case 3: {
        Wrapper<3> w;
        w.foo();
        break;
    }
    default:
        return 1;
    };

    return 0;
}

This will quickly get unwieldy once I have not only one parameter A, but multiple template arguments in various combinations. Let's also assume that in reality there is a really good reason to implement A as a template parameter.

Is there a way to replace the huge switch statement with almost identical case statements, e.g. using some metaprogramming magic from Boost or a preprocessor hack?

Ideally I would like to be able write something like the following:

INSTANTIATE_DEPENDING(i, {1, 2, 3},
            {
                Wrapper<i> w;
                w.foo();
            }
    );
like image 416
rerx Avatar asked Aug 08 '14 11:08

rerx


People also ask

How do I force a template instantiation?

To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>(float original); Template arguments may be omitted when the compiler can infer them.

Is it necessary to instantiate a template?

Class template instantiationIn order for any code to appear, a template must be instantiated: the template arguments must be provided so that the compiler can generate an actual class (or function, from a function template).

What is the main problem with templates C++?

Can get complicated quickly if one isn't careful. Most compilers give cryptic error messages. It can be difficult to use/debug highly templated code. Have at least one syntactic quirk ( the >> operator can interfere with templates)

How will you restrict the template for a specific datatype?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.


3 Answers

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...>());
}
like image 116
Kerrek SB Avatar answered Sep 27 '22 16:09

Kerrek SB


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.

like image 40
CashCow Avatar answered Sep 27 '22 17:09

CashCow


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.

like image 41
Stefan Weiser Avatar answered Sep 27 '22 17:09

Stefan Weiser