[英]Replacing switch statements when interfacing between templated and non-templated code
The X: 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: 我在实施过程中遇到的问题:
template <typename T, T VAL> struct value_container { static constexpr T value() {return VAL;} };
) 模板参数必须是类型,必须将值包装在类型中( template <typename T, T VAL> struct value_container { static constexpr T value() {return VAL;} };
) And the only pros: 唯一的优点:
Has anyone do something similar or have a better way? 有没有人做类似的事情或有更好的方法?
You can always walk a type list indexed by type_param
, as in: 您始终可以遍历由type_param
索引的类型列表,如下所示:
struct foo
{
virtual ~foo() = default;
/* ... */
};
template<typename T>
struct bar : foo
{ /* ... */ };
template<typename TL>
struct foo_maker;
template<template<typename...> class TL, typename T, typename... Ts>
struct foo_maker<TL<T, Ts...>>
{
template<typename... Us>
std::unique_ptr<foo> operator()(int i, Us&&... us) const
{
return i == 1 ?
std::unique_ptr<foo>(new bar<T>(std::forward<Us>(us)...)) :
foo_maker<TL<Ts...>>()(i - 1, std::forward<Us>(us)...); }
};
template<template<typename...> class TL>
struct foo_maker<TL<>>
{
template<typename... Us>
std::unique_ptr<foo> operator()(int, Us&&...) const
{ return nullptr; }
};
template<typename...>
struct types;
template<typename... Us>
std::unique_ptr<foo> make_foo(int typed_param, Us&& us...)
{ return foo_maker<types<int, float, double, uint8_t>>()(typed_param, std::forward<Us>(us)...); };
Note: this factory function is O(n) (although a clever compiler could make it O(1)), while the switch
statement version is O(1). 注意:此工厂函数为O(n)(尽管聪明的编译器可以将其设置为O(1)),而switch
语句的版本为O(1)。
Just to expand YoungJohn's comment, it looks like this (I've included a single initialization of the operator, and it could be made simpler if there was no parameters, but if there are no parameters there is little reason to do this anyway :-P). 只是为了扩展YoungJohn的注释,它看起来像这样(我已经包含了一个运算符的初始化,并且如果没有参数,可以使它更简单,但是如果没有参数,那么反正没有什么理由:- P)。
#include <functional>
#include <map>
////////////////////////////////////
//////specific impmenetation 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
{
int some_param_; /// <shared parameter
my_op(int some_param) : some_param_(some_param){} /// <constructor
template<typename T>
base* do_stuff() const
{
std::cout << "Use some parameter: " << some_param_ << std::endl;
base* ret = new derived<T>();
ret->foo();
return ret;
}
};
base* init_from_params(int some_param, char key)
{
my_op op(some_param);
using factoryFunction = std::function<base*()>;
std::map<char, factoryFunction> mp
{
{ 'a', std::bind(&my_op::do_stuff<int>, &op)},
{ 'b', std::bind(&my_op::do_stuff<long int>, &op)},
{ 'c', std::bind(&my_op::do_stuff<double>, &op)}
} ;
factoryFunction& f = mp[key];
if (f)
{
return f();
}
return NULL;
}
int main(int argc, char** argv)
{
volatile int parameters = 10;
while (true)
{
std::cout << "Press a, b, or c" << std::endl;
char key;
std::cin >> key;
base* value = init_from_params(parameters, key);
std::cout << (void*)value << std::endl;
}
}
Pros: so much shorter, so much more standard, so much less weird template stuff. 优点:更短,更标准,更不奇怪的模板内容。 It also doesn't require the templated arguments to all be types, we can select whatever we want to initialize the function. 它还不需要模板化的参数都必须是类型,我们可以选择要初始化函数的任何内容。
Cons: In theory, it could have more overhead. 缺点:从理论上讲,它可能会有更多开销。 In practice, I totally doubt that the overhead would ever matter. 实际上,我完全怀疑开销会不会很重要。
I like it! 我喜欢!
template<class T>
foo* make_foo(int typed_param,/*more params*/)
{
return new bar<T>(/*more params*/);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.