简体   繁体   English

在模板化和非模板化代码之间进行接口时替换switch语句

[英]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: 我在实施过程中遇到的问题:

  1. It is clear and 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;} }; ) 模板参数必须是类型,必须将值包装在类型中( 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, then we can be a little shorter. 如果case语句变高/ do_things的内容变高,那么我们可以短一些。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM