在模板化和非模板化代码之间接口时替换switch语句 [英] Replaceing switch statements when interfaceing between templated and non-templated code

查看:141
本文介绍了在模板化和非模板化代码之间接口时替换switch语句的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

X:



我看到的一个常见模式是一个函数的底层代码是模板,但由于原因模板代码不可用在上层(从接口中的aversion到模板的选择,需要一个共享库,而不是将实现暴露给客户,在运行时读取类型设置而不是编译时间等)。



这通常做的是做以下事情:

  struct foo {virtual void foo() } 
template< typename T> struct bar:public foo
{
bar(/ * could be lot here * /);
virtual void foo(){/ * something complex,but type specific * /}
};

然后进行初始化调用:

  foo * make_foo(int typed_pa​​ram,/ * more params * /)
{
switch(typed_pa​​ram)
{
case 1:return new bar< int>(/ * more params * /);
case 2:return new bar< float>(/ * more params * /);
case 3:return new bar< double>(/ * more params * /);
case 4:return new bar< uint8_t>(/ * more params * /);
默认值:return NULL;
}
}

这是烦人,重复和容易出错的代码。



所以我对自己说,自我说我有GOT是一个更好的方式。



Y:



我做了。你们都有更好的方法吗?

  ////////////////// ////////////////// 
//////在整个地方重用的代码
///
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& 。>();
}
};

template< typename T>
struct error_select
{
T操作符()()const {throw std :: out_of_range(无匹配);}
};

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 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
{
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< 按a,b或c< std :: endl;
char key;
std :: cin>>键;

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;
}

/ *把这里作为参考,做同样的事情,但旧的方式:* /
/ *
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;
}
默认值:
return NULL;
}
* /
}

实现:


  1. 很清楚可读为泥巴

  2. 模板参数必须是类型,类型中的换行值( template< typename T,T VAL> struct value_container {static constexpr T value(){return VAL;}};

  3. 目前没有检查/强制选择器都是类型 - 值对。

p>


  1. 删除代码重复。

  2. 如果case语句变高/ do_things的内容变高,

任何人做类似或更好的方式吗?

  #include< functional> 
#include< map>


////////////////////////////////////
//////特定的impmenetation代码
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 param:<< 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< 按a,b或c< std :: endl;
char key;
std :: cin>>键;

base * value = init_from_params(parameters,key);

std :: cout<< (void *)value<< std :: endl;优点:这么短,所以更多的标准,更多的,更多的,更多的,所以不那么奇怪的模板东西。它也不需要所有的模板参数是类型,我们可以选择任何我们想要初始化的函数。



缺点:在理论上,它可以有更多的开销。在实践中,我完全怀疑这个开销会有影响。



我喜欢它!


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, ect).

What this often does is make 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 params*/)
{
   switch(typed_param)
   {
   case 1: return new bar<int>(/*more params*/);
   case 2: return new bar<float>(/*more params*/);
   case 3: return new bar<double>(/*more params*/);
   case 4: return new bar<uint8_t>(/*more params*/);
   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 dis. 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 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
{
  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;
  }

  /*putting this in here for reference, 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:

  1. It is clear an readable as mud
  2. 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;} };)
  3. Currently no checking/forcing that the selectors are all type-value pairs.

and the only pros:

  1. Removes code duplication.
  2. If the case statement gets high/the contents of do_things gets high, than we can be a little shorter.

Anyone do something similar or have a better way?

解决方案

Just to expand YoungJohn's comment, it looks like this (i've included a single initialization of the operator, it could be made simpler if there was no params, but if there is no params there is little reason to do this anyway :-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 param: " << 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!

这篇关于在模板化和非模板化代码之间接口时替换switch语句的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆