带有模板参数的c++函数分派
C++ function dispatch with template parameters
我正在重构一个大型类——我们称之为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));
}
除了:
- 它在Visual c++ 9(2008)下构建和工作良好,但在GCC 4.8下,它会导致
lookup()
的函数模板过载编译错误。 - 它要求为
go()
中我们想要支持的每个新数量的函数模板参数编写一个新的lookup()
函数模板重载。 使用起来又麻烦又混乱。
下面是在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>;
^
我的问题是双重的:
- 为什么在GCC下构建失败,我如何修复它?
- 是否有更好的(即不那么麻烦和令人困惑的)方法来做到这一点?
代码必须在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},
};
相关文章:
- "error: no matching function for call to"构造函数错误
- 什么时候调用组成单元对象的析构函数
- 继承函数的重载解析
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- C++模板来检查友元函数的存在
- 递归函数计算序列中的平方和(并输出过程)
- 对RValue对象调用的LValue ref限定成员函数
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 在C++STL中是否有Polyval(Matlab函数)等价物?
- 为什么使用 "this" 指针调用派生成员函数?
- 将对象数组的引用传递给函数
- 函数调用中参数的顺序重要吗
- 函数向量_指针有不同的原型,我可以构建一个吗
- 使用不带参数的函数访问结构元素
- 代码在main()中运行,但在函数中出现错误
- 内置函数可查看CPP中的成员变量
- 如何获取std::result_of函数的返回类型
- 如何在c++中为模板函数实例创建快捷方式
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗