The X:
A common pattern I'm seeing is that the underlying code for a function is templates, but for "reasons" the template code is not available at the upper layer (pick from aversion to templates in interface, the need for a shared library and not to expose implementation to customer, reading type settings at run time instead of compile time, etc.).
This often makes the following:
struct foo { virtual void foo() = 0;}
template <typename T> struct bar : public foo
{
bar( /* Could be lots here */);
virtual void foo() { /* Something complicated, but type specific */}
};
And then an initialize call:
foo* make_foo(int typed_param, /* More parameters */)
{
switch(typed_param)
{
case 1: return new bar<int>(/* More parameters */);
case 2: return new bar<float>(/* More parameters */);
case 3: return new bar<double>(/* More parameters */);
case 4: return new bar<uint8_t>(/* More parameters */);
default: return NULL;
}
}
This is annoying, repetitive, and error prone code.
So I says to myself, self says I, there has GOT to be a better way.
The Y:
I made this. Do you all have a better way?
////////////////////////////////////
//////Code to reuse all over the place
///
template <typename T, T VAL>
struct value_container
{
static constexpr T value() {return VAL;}
};
template <typename J, J VAL, typename... Ts>
struct type_value_pair
{
static constexpr J value() {return VAL;}
template <class FOO>
static auto do_things(const FOO& foo)->decltype(foo.template do_things<Ts...>()) const
{
foo.template do_things<Ts...>();
}
};
template <typename T>
struct error_select
{
T operator()() const { throw std::out_of_range("no match");}
};
template <typename T>
struct default_select
{
T operator()() const { return T();}
};
template <typename S, typename... selectors>
struct type_selector
{
template <typename K, class FOO, typename NOMATCH, typename J=decltype(S::do_things(FOO()))>
static constexpr J select(const K& val, const FOO& foo=FOO(), const NOMATCH& op=NOMATCH())
{
return S::value()==val ? S::do_things(foo) : type_selector<selectors...>::template select<K, FOO, NOMATCH, J>(val, foo, op);
}
};
template <typename S>
struct type_selector<S>
{
template <typename K, class FOO, typename NOMATCH, typename J>
static constexpr J select(const K& val, const FOO& foo=FOO(), const NOMATCH& op=NOMATCH())
{
return S::value()==val ? S::do_things(foo) : op();
}
};
////////////////////////////////////
////// Specific implementation code
class base{public: virtual void foo() = 0;};
template <typename x>
struct derived : public base
{
virtual void foo() {std::cout << "Ima " << typeid(x).name() << std::endl;}
};
struct my_op
{
template<typename T>
base* do_things() const
{
base* ret = new derived<T>();
ret->foo();
return ret;
}
};
int main(int argc, char** argv)
{
while (true)
{
std::cout << "Press a,b, or c" << std::endl;
char key;
std::cin >> key;
base* value = type_selector<
type_value_pair<char, 'a', int>,
type_value_pair<char, 'b', long int>,
type_value_pair<char, 'c', double> >::select(key, my_op(), default_select<base*>());
std::cout << (void*)value << std::endl;
}
/* I am putting this in here for reference. It does the same
thing, but the old way: */
/*
switch(key)
{
case 'a':
{
base* ret = new derived<int>();
ret->foo();
value = ret;
break;
}
case 'b':
{
base* ret = new derived<char>();
ret->foo();
value = ret;
break;
}
case 'c':
{
base* ret = new derived<double>();
ret->foo();
value = ret;
break;
}
default:
return NULL;
}
*/
}
Problems I see with my implementation:
- It is clear and readable as mud
- Template parameters MUST be types, have to wrap values in types (
template <typename T, T VAL> struct value_container { static constexpr T value() {return VAL;} };
) - Currently no checking/forcing that the selectors are all type-value pairs.
And the only pros:
- Removes code duplication.
- If the case statement gets high/the contents of do_things gets high, then we can be a little shorter.
Has anyone do something similar or have a better way?
See Question&Answers more detail:os