C++ Template Instantiation: Avoiding Long Switches

C++ template instantiation: Avoiding long switches

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

C++ templates to avoid long switches, while calling a function with different return types

If you can use c++17, here's a "simplified" version of @Klaus's approach. Instead of using a had-made recursive structure, you could use a c++17 fold-expression:

template<auto... Funcs, std::size_t... I>
bool select_case(std::size_t i, std::integer_sequence<std::size_t, I...>) {
return ([&]{ if(i == I) { print_result(Funcs); return true; } return false; }() || ... );
}

template<auto... Funcs>
struct FuncSwitch {

static bool Call(std::size_t i) {
return select_case<Funcs...>(i, std::make_index_sequence<sizeof...(Funcs)>());
}
};

The idea is to wrap each of Funcs in a lambda such that only the function corresponding to the index passed is called. Note that the || in the fold expression short-circuits.
Would be used like this:

float q0() { return 0.f; }
int q1() { return 1; }
std::string q2() { return "two"; }

int main() {

bool success = FuncSwitch<q0, q1, q2>::Call(1);
}

See here for a complete example.

Choosing a template instantiation at runtime though switch in C++

Template template parameters are the key:

enum Index { One, Two, Three, Four };

template <template <Index> class Switcher, typename T>
void barSwitch(int k, const T & x)
{
switch (k)
{
case 1: Switcher<One>::template bar<T>(x); break;
case 2: Switcher<Two>::template bar<T>(x); break;
default: assert(false);
}
}

Usage:

template <Index I> struct Foo
{
template <typename T> static void bar(const T & x);
};

barSwitch<Foo>(1, Blue);

(It is your responsibility to ensure that every possible template that you substitute for Switcher has a member template bar, of course. If not, you'll get a compile error.)

Using template instead of switch

Building off Andrew's answer...

Note that the EnumSensorFamily family must be known at compile time. If it is not known until run time, then you'll have to write a switch to choose the template, putting you back where you started.

Another way to do this is with the Traits pattern:

template <EnumSensorFamily family>
struct SensorTraits;

template <>
struct SensorTraits<FAM1>
{
const EnumSensorFamily kFamilyID = ExpectedFam1;
};

template <>
struct SensorTraits<FAM2>
{
const EnumSensorFamily kFamilyID = ExpectedFam2;
};

template <>
struct SensorTraits<FAM3>
{
const EnumSensorFamily kFamilyID = ExpectedFam3;
};

template <EnumSensorFamily family>
bool doTest(const StructSensorProposal& proposed)
{
return (SensorTraits<family>::kFamilyID == proposed.Fam1SensorId);
}

If you try to use doTest with a sensor family that lacks a traits specialization, you get a compile error. Also note that you never instantiate a traits object, you just use its definitions.

This lets you reuse constants, typedefs, whatever in several functions. Additionally, adding a new family does not involve combing through all the code looking for every switch statement that cares. All you have to do is create a new SensorTraits specialization.

EDIT: You can make the field dependent on the sensor family with a pointer to member:

template <>
struct SensorTraits<FAM1>
{
const EnumSensorFamily kFamilyID = ExpectedFam1;
int StructSensorProposal::*proposalField = &StructSensorProposal::fam1field;
};

// ...

template <EnumSensorFamily family>
int getProposedField(const StructSensorProposal& proposed)
{
return proposed.*SensorTraits<family>::proposalField;
}

You can also put in, say, a typedef for the sensor's data type:

template <>
struct SensorTraits<FAM1>
{
const EnumSensorFamily kFamilyID = ExpectedFam1;
typedef uint16_t data_type;
data_type StructSensorProposal::*proposalField = &StructSensorProposal::fam1field;
};

// ...

template <EnumSensorFamily family>
SensorTraits<family>::data_type getProposedField(const StructSensorProposal& proposed)
{
return proposed.*SensorTraits<family>::proposalField;
}

I haven't tested these; you might need a const or static in there.

Optimize template replacement of a switch

This is what I call the magic switch problem -- how to take a (range of) run time values and turn it into a compile time constant.

Abstractly, you want to generate this switch statement:

switch(n) {
(case I from 0 to n-1: /* use I as a constant */)...
}

You can use parameter packs to generate code that is similar to this in C++.

I'll start with c++14-replacing boilerplate:

template<unsigned...> struct indexes {typedef indexes type;};
template<unsigned max, unsigned... is> struct make_indexes: make_indexes<max-1, max-1, is...> {};
template<unsigned... is> struct make_indexes<0, is...>:indexes<is...> {};
template<unsigned max> using make_indexes_t = typename make_indexes<max>::type;

Now we can create a compile-time sequence of unsigned integers from 0 to n-1 easily. make_indexes_t<50> expands to indexes<0,1,2,3, ... ,48, 49>. The c++14 version does so in O(1) steps, as most (all?) compilers implement std::make_index_sequence with an intrinsic. The above does it in linear (at compile time -- nothing is done at run time) recursive depth, and quadratic compile time memory. This sucks, and you can do better with work (logarithmic depth, linear memory), but do you have more than a few 100 types? If not, this is good enough.

Next, we build an array of callbacks. As I hate C legacy function pointer syntax, I'll throw in some pointless boilerplate to hide it:

template<typename T> using type = T; // pointless boilerplate that hides C style function syntax

template<unsigned... Is>
Base_Type construct_runtime_helper( indexes<Is...>, Base_Type::type_enum e, QVariant const& v ) {
// array of pointers to functions: (note static, so created once)
static type< Base_Type(const QVariant&) >* const constructor_array[] = {
(&Base_Type::construct<Is>)...
};
// find the eth entry, and call it:
return constructor_array[ unsigned(e) ](v);
}
Base_Type construct_runtime_helper( Base_Type::type_enum e, QVariant const& v ) {
return construct_runtime_helper( make_indexes_t< Base_Type::num_types >(), e, v );
}

and Bob is your Uncle1. An O(1) array lookup (with an O(n) setup, which in theory could be done prior to your executable launching) for dispatch.


1 "Bob's your Uncle" is a British Commonwealth saying that says "and everything is finished and working" roughly.

template instantiation with boost: pass extra arguments

i ended up using

# include <boost/preprocessor/facilities/empty.hpp>
# include <boost/preprocessor/list/at.hpp>
# include <boost/preprocessor/list/for_each_product.hpp>
# include <boost/preprocessor/tuple/elem.hpp>
# include <boost/preprocessor/tuple/to_list.hpp>

#define DIM BOOST_PP_TUPLE_TO_LIST(2,(2,3))
#define DEG BOOST_PP_TUPLE_TO_LIST(1,(typeA))

#define INSTANTIATE(R, L) \
template void MyClass<BOOST_PP_TUPLE_ELEM(2, 0, L), \
BOOST_PP_TUPLE_ELEM(2, 1, L)>::some_method() const;
BOOST_PP_LIST_FOR_EACH_PRODUCT(INSTANTIATE, 2, (DIM, DEG))

Map value to type to avoid switch statement in C++

You can get rid of the switch statement using a registration/plugin mechanism.

Interface for registering functions and using them:

typedef void (*MessageDispatcher)(const vector<byte>& bytes);

void registerMessageDispatcher(Id id, MessageDispatcher dispatcher);

void dispatchMessage(Id id, const vector<byte>& bytes);

In the implementation:

static std::map<Id, MessageDispatcher> messageDispatcherMap;

void registerMessageDispatcher(Id id, MessageDispatcher dispatcher)
{
messageDispatcherMap[id] = dispatcher;
}

void dispatchMessage(Id id, const vector<byte>& bytes)
{
std::map<Id, MessageDispatcher>::iterator iter = messageDispatcherMap.find(id);
if ( iter == messageDispatcherMap.end() )
{
// Deal with the error condition.
return;
}

// Dispatch the message.
iter->second(bytes);

}

Create functions for various message types.

void dispatchMessage1(const vector<byte>& bytes)
{
Message1 message = convert_bytes<Message1> (bytes);
notify (message);
}

void dispatchMessage2(const vector<byte>& bytes)
{
Message2 message = convert_bytes<Message2> (bytes);
notify (message);
}

void dispatchMessage3(const vector<byte>& bytes)
{
Message3 message = convert_bytes<Message3> (bytes);
notify (message);
}

etc...

Register the functions.

registerMessageDispatcher(ID::Message1, dispatchMessage1);
registerMessageDispatcher(ID::Message2, dispatchMessage2);
registerMessageDispatcher(ID::Message3, dispatchMessage3);

Now, the code to deal with the message will be:

auto bytes = receive ();
Id id = get_id (bytes);
dispatchMessage(id, bytes);


Related Topics



Leave a reply



Submit