【问题标题】:C++ function dispatch with template parameters带有模板参数的 C++ 函数分派
【发布时间】:2013-05-14 20:22:10
【问题描述】:

我正在重构一个包含大量复制粘贴代码的大型类——我们称之为Big。大部分复制粘贴代码存在于switchcases 中,其中只有涉及的类型最终不同。代码基于类的 enum 成员变量进行切换,其值仅在运行时才知道。

我试图解决这个问题的方法是创建一个Dispatcher 类,该类通过一个名为lookup()static 函数查找适当类型的函数。执行实际工作的函数始终称为go(),并且必须在包装类模板中定义(其唯一参数是当前打开的运行时enum 值)。 go() 函数本身可能是也可能不是模板函数。

这是代码的精简版。对于篇幅,我深表歉意,但在不丢失重要背景的情况下,我尽可能地简短。

#include <cassert>

class Big
{
    public:

        enum RuntimeValue { a, b };

        Big(RuntimeValue rv) : _rv(rv) { }

        bool equals(int i1, int i2)
        {
            return Dispatcher<Equals, bool(int, int)>::lookup(_rv)(i1, i2);
        }

        template<typename T>
        bool isConvertibleTo(int i)
        {
            return Dispatcher<IsConvertibleTo, bool(int)>::lookup<T>(_rv)(i);
        }

    private:

        template<RuntimeValue RV>
        struct Equals
        {
            static bool go(int i1, int i2)
            {
                // Pretend that this is some complicated code that relies on RV
                // being a compile-time constant.
                return i1 == i2;
            }
        };

        template<RuntimeValue RV>
        struct IsConvertibleTo
        {
            template<typename T>
            static bool go(int i)
            {
                // Pretend that this is some complicated code that relies on RV
                // being a compile-time constant.
                return static_cast<T>(i) == i;
            }
        };

        template<template<RuntimeValue> class FunctionWrapper, typename Function>
        struct Dispatcher
        {
            static Function * lookup(RuntimeValue rv)
            {
                switch (rv)
                {
                    case a: return &FunctionWrapper<a>::go;
                    case b: return &FunctionWrapper<b>::go;

                    default: assert(false); return 0;
                }
            }

            template<typename T>
            static Function * lookup(RuntimeValue rv)
            {
                switch (rv)
                {
                    case a: return &FunctionWrapper<a>::go<T>;
                    case b: return &FunctionWrapper<b>::go<T>;

                    default: assert(false); return 0;
                }
            }

            // And so on as needed...
            template<typename T1, typename T2>
            static Function * lookup(RuntimeValue rv);
        };

        RuntimeValue _rv;
};

int main()
{
    Big big(Big::a);

    assert(big.equals(3, 3));
    assert(big.isConvertibleTo<char>(123));
}

这主要是有效的,除了:

  1. 它在 Visual C++ 9 (2008) 下可以正常构建和工作,但在 GCC 4.8 下会导致 lookup() 的函数模板重载时出现编译错误。
  2. 它要求为go() 中我们希望支持的每个新的函数模板参数数量编写一个新的函数模板重载lookup()
  3. 使用起来既麻烦又混乱。

以下是在 GCC 下发生的错误:

Big.cpp: In static member function 'static Function* Big::Dispatcher<FunctionWrapper, Function>::lookup(Big::RuntimeValue)':
Big.cpp(66,65) : error: expected primary-expression before '>' token
                         case a: return &FunctionWrapper<a>::go<T>;
                                                                 ^
Big.cpp(66,66) : error: expected primary-expression before ';' token
                         case a: return &FunctionWrapper<a>::go<T>;
                                                                  ^
Big.cpp(67,65) : error: expected primary-expression before '>' token
                         case b: return &FunctionWrapper<b>::go<T>;
                                                                 ^
Big.cpp(67,66) : error: expected primary-expression before ';' token
                         case b: return &FunctionWrapper<b>::go<T>;
                                                                  ^

我的问题有两个:

  1. 为什么在 GCC 下构建失败,我该如何解决?
  2. 是否有更好的(即不那么繁琐和混乱的)方法来做到这一点?

代码必须在 Visual C++ 9 (2008) 下可编译,所以我不能使用任何 C++11 特定的代码。

【问题讨论】:

    标签: c++ templates dispatch


    【解决方案1】:

    由于go是模板的依赖名,所以需要使用template消歧符:

    case a: return &FunctionWrapper<a>::template go<T>;
    //                                  ^^^^^^^^
    case b: return &FunctionWrapper<b>::template go<T>;
    //                                  ^^^^^^^^
    

    这告诉编译器将范围解析运算符 (::) 后面的内容解析为模板的名称,并将随后的尖括号作为模板参数的分隔符。

    为什么在 GCC 下构建失败,我该如何解决?

    因为 GCC 符合标准并执行 two-phase name lookup,而 MSVC 将名称查找延迟到实例化时间,因此知道 go 是模板的名称。

    在实例化之前,此信息不可用,因为不可能知道T 是什么,并且主模板可以专门用于给定的T,因此go 不是成员函数模板的名称,而是数据成员。

    也就是说,我希望 MSVC 无论如何都支持 template 消歧器,因此添加它应该使您的程序在 GCC/Clang/whatever-conforms-to-the-Standard 和 MSVC 上都可以编译。

    【讨论】:

    • 感谢您的回答。你对我问题的第二部分有什么建议吗?虽然这个方案有效(感谢您的帮助),但我对它并不满意。我曾探索过使用虚函数作为替代方案,但当我意识到这需要 C++ 不支持的虚函数模板时,我碰壁了。
    • @Spire:我必须承认我没有花时间分析设计和你的程序实际做了什么,我只是发现了这两个错误并认为我会发布一个答案。不幸的是,我现在没有时间研究它(我的程序中也有丑陋的错误要杀死;))
    【解决方案2】:

    我最近写了一个命令调度器:

    #include <map>
    
    // because std::invoke is not in this compiler version.
    #define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
    
    template <class MyType, class cmd_type, class ret_type, typename... Args>
    class CommandDispatcher {
        typedef ret_type (MyType::*CommandFunction)(Args... args);
    public:
        // create using static/existing map
        CommandDispatcher(std::map<cmd_type, CommandFunction>& cmd_map) : _command_table(cmd_map) {}
    
        ret_type operator()(MyType& my_obj, cmd_type cmd, Args... args) 
        {
            int retval = 0;
    
            if (_command_table.find(cmd) == _command_table.end()) {
                std::cerr << "No command implementation found: " << cmd << endl;
                return -EINVAL;
            }
            return CALL_MEMBER_FN(my_obj, _command_table[cmd])(args...);
        }
    private:
        std::map<cmd_type, CommandFunction>& _command_table;
    };
    

    使用它看起来像:

    class MyClass {
    public:
        MyClass() : _dispatcher(_command_map) {}
    private:
        static std::map<int,  CommandFunction> _command_map;
        CommandDispatcher<MyClass, int, int, const char*, int> _dispatcher;
    };
    

    在 cpp 中:

    std::map<int, CommandFunction> MyClass::_command_map{
        {E_CMD1, &MyClass::Cmd1},
        {E_CMD2, &MyClass::Cmd2},
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-20
      • 2020-10-26
      • 1970-01-01
      • 2017-06-24
      • 2021-05-28
      • 2013-02-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多