带有模板参数的c++函数分派

C++ function dispatch with template parameters

本文关键字:c++ 函数 分派 参数      更新时间:2023-10-16

我正在重构一个大型类——我们称之为Big——它有大量的复制粘贴代码。这种复制粘贴代码大部分存在于switch case中,只是涉及的类型最终不同。代码根据类的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的代码

由于go是模板的依赖名称,因此您需要使用template消歧器:

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

告诉编译器解析作用域解析操作符(::)后面的内容作为模板名,后面的尖括号作为模板参数的分隔符。

为什么在GCC下构建失败,我如何修复它?

因为GCC符合标准,并且执行两阶段的名称查找,而MSVC将名称查找延迟到实例化时间,因此,知道go是模板的名称。

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

这就是说,我希望MSVC无论如何都支持template消歧器,所以添加它应该使您的程序在GCC/Clang/任何符合标准的程序和MSVC上都可以编译。

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

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

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