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

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

问题描述

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

这通常会使以下内容:

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

然后进行初始化调用:

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.

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

我看到的问题实现:


  1. 它很清楚,可读性很强

  2. 模板参数必须是类型,必须将值包装为类型( template< typename T,T VAL> struct value_container {static constexpr T value(){return VAL;}};

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

唯一的优点:


  1. 删除代码重复。

  2. 如果case语句变高/ do_things的内容变高,则我们可以缩短一点。

有人有做类似的事情或有更好的方法吗?

Has anyone do something similar or have a better way?

推荐答案

只是为了扩展YoungJohn的注释,它看起来像这样(我已经对运算符进行了一次初始化,如果没有参数,可以简化它,但是如果没有参数

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

#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.

我喜欢它!

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

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