但是,我决定实现一个更为复杂的解决方案,以便能够处理任何带有任何返回类型的模板化函数(方法),因为在C ++世界中,模板很常见。 在我的答案末尾提供了完整的代码,并提供了许多示例,代码可以复制粘贴以在您自己的项目中使用。
为了在单个函数中返回许多可能的类型,我使用了std::variant,这是C++17标准类。
使用我的完整代码(在答案底部),您可以执行以下操作:
using MR = MethodsRunner<
Cmd<1, &A::Add3>,
Cmd<2, &A::AddXY<long long>>,
Cmd<3, &B::ToStr<std::integral_constant<int, 17>>>,
Cmd<4, &B::VoidFunc>
>;
A a;
B b;
auto result1 = MR::Run(1, a, 5);
auto result2 = MR::Run(2, a, 3, 7);
auto result3 = MR::Run(3, b);
auto result4 = MR::Run(4, b, 12, true);
auto result4_bad = MR::Run(4, b, false);
auto result5 = MR::Run(5, b);
这里的A
和B
是任意类。对于MethodsRunner
,您提供了一个命令列表,由命令ID和指向方法的指针组成。只要您提供了它们调用的完整签名,就可以提供指向任何模板化方法的指针。
MethodsRunner
在被.Run()
调用时返回包含所有可能值的std::variant
,这些值具有不同的类型。您可以通过std::get(variant)访问变体的实际值,或者如果您事先不知道包含的类型,则可以使用std::visit(lambda, variant)。
我在我的类中使用了几个小型辅助模板结构体,这种元编程在模板化的C++世界中非常常见。
代码的完整示例(答案末尾)展示了我MethodsRunner的所有用法类型。
我在我的解决方案中使用了switch
结构,而不是std::vector<std::function<void()>>
,因为只有switch
才能处理任意参数类型和数量以及任意返回类型的一组数据。只有在所有命令具有相同的参数类型和返回值时,才可以使用std::function
表代替switch
。
众所周知,如果switch和case的值是整数,则所有现代编译器都将switch
实现为直接跳转表。换句话说,switch
解决方案与常规的std::vector<std::function<void()>>
函数表方法一样快,甚至更快。
我的解决方案应该非常高效,尽管它似乎包含了很多代码,但所有重型模板化的代码都被折叠成非常小的实际运行时代码,基本上只有一个switch
表直接调用所有方法,再加上返回值的std::variant转换,几乎没有任何开销。
我预计您使用的命令ID在编译时是未知的,而是只有在运行时才知道。如果在编译时已知,则根本不需要switch
,基本上可以直接调用给定的对象。
我的Run方法的语法是method_runner.Run(cmd_id, object, arguments...)
,在这里您提供任何仅在运行时已知的命令ID,然后提供任何对象和任何参数。如果您只有一个实现所有命令的单个对象,则可以像下面这样使用我在代码中实现的SingleObjectRunner
:
SingleObjectRunner<MR, A> ar(a);
ar(1, 5);
ar(2, 3, 7);
SingleObjectRunner<MR, B> br(b);
br(3);
br(4, 12, true);
其中MR
是专为所有命令定制的MethodsRunner
类型。这里,单个对象运行器ar
和br
都可调用,就像函数一样,签名为(cmd_id, args...)
,例如br(4, 12, true)
调用意味着命令ID为4
,参数为12, true
,并且b
对象本身是通过br(b);
在构造时捕获到的SingleObjectRunner内部。
请查看代码后详细的控制台输出日志。还请注意代码和日志之后的重要注释。完整代码如下:
在线尝试!
#include <iostream>
#include <type_traits>
#include <string>
#include <any>
#include <vector>
#include <tuple>
#include <variant>
#include <iomanip>
#include <stdexcept>
#include <cxxabi.h>
template <typename T>
inline std::string TypeName() {
int status = 0;
return abi::__cxa_demangle(typeid(T).name(), 0, 0, &status);
}
struct NotCallable {};
struct VoidT {};
template <size_t _Id, auto MethPtr>
struct Cmd {
static size_t constexpr Id = _Id;
template <class Obj, typename Enable = void, typename ... Args>
struct Callable : std::false_type {};
template <class Obj, typename ... Args>
struct Callable<Obj,
std::void_t<decltype(
(std::declval<Obj>().*MethPtr)(std::declval<Args>()...)
)>, Args...> : std::true_type {};
template <class Obj, typename ... Args>
static auto Call(Obj && obj, Args && ... args) {
if constexpr(Callable<Obj, void, Args...>::value) {
if constexpr(std::is_same_v<void, std::decay_t<decltype(
(obj.*MethPtr)(std::forward<Args>(args)...))>>) {
(obj.*MethPtr)(std::forward<Args>(args)...);
return VoidT{};
} else
return (obj.*MethPtr)(std::forward<Args>(args)...);
} else {
throw std::runtime_error("Calling method '" + TypeName<decltype(MethPtr)>() +
"' with wrong object type and/or wrong argument types or count and/or wrong template arguments! "
"Object type '" + TypeName<Obj>() + "', tuple of arguments types '" + TypeName<std::tuple<Args...>>() + "'.");
return NotCallable{};
}
}
};
template <typename T, typename ... Ts>
struct HasType;
template <typename T>
struct HasType<T> : std::false_type {};
template <typename T, typename X, typename ... Tail>
struct HasType<T, X, Tail...> {
static bool constexpr value = std::is_same_v<T, X> ||
HasType<T, Tail...>::value;
};
template <typename T> struct ConvVoid {
using type = T;
};
template <> struct ConvVoid<void> {
using type = VoidT;
};
template <typename V, typename ... Ts>
struct MakeVariant;
template <typename ... Vs>
struct MakeVariant<std::variant<Vs...>> {
using type = std::variant<Vs...>;
};
template <typename ... Vs, typename T, typename ... Tail>
struct MakeVariant<std::variant<Vs...>, T, Tail...> {
using type = std::conditional_t<
HasType<T, Vs...>::value,
typename MakeVariant<std::variant<Vs...>, Tail...>::type,
typename MakeVariant<std::variant<Vs...,
typename ConvVoid<std::decay_t<T>>::type>, Tail...>::type
>;
};
template <typename ... Cmds>
class MethodsRunner {
public:
using CmdsTup = std::tuple<Cmds...>;
static size_t constexpr NumCmds = std::tuple_size_v<CmdsTup>;
template <size_t I> using CmdAt = std::tuple_element_t<I, CmdsTup>;
template <size_t Id, size_t Idx = 0>
static size_t constexpr CmdIdToIdx() {
if constexpr(Idx < NumCmds) {
if constexpr(CmdAt<Idx>::Id == Id)
return Idx;
else
return CmdIdToIdx<Id, Idx + 1>();
} else
return NumCmds;
}
template <typename Obj, typename ... Args>
using RetType = typename MakeVariant<std::variant<>, decltype(
Cmds::Call(std::declval<Obj>(), std::declval<Args>()...))...>::type;
template <typename Obj, typename ... Args>
static RetType<Obj, Args...> Run(size_t cmd, Obj && obj, Args && ... args) {
#define C(Id) \
case Id: { \
if constexpr(CmdIdToIdx<Id>() < NumCmds) \
return CmdAt<CmdIdToIdx<Id>()>::Call( \
obj, std::forward<Args>(args)... \
); \
else goto out_of_range; \
}
switch (cmd) {
C( 0) C( 1) C( 2) C( 3) C( 4) C( 5) C( 6) C( 7) C( 8) C( 9)
C( 10) C( 11) C( 12) C( 13) C( 14) C( 15) C( 16) C( 17) C( 18) C( 19)
default:
goto out_of_range;
}
#undef C
out_of_range:
throw std::runtime_error("Unknown command " + std::to_string(cmd) +
"! Number of commands " + std::to_string(NumCmds));
}
};
template <typename MR, class Obj>
class SingleObjectRunner {
public:
SingleObjectRunner(Obj & obj) : obj_(obj) {}
template <typename ... Args>
auto operator () (size_t cmd, Args && ... args) {
return MR::Run(cmd, obj_, std::forward<Args>(args)...);
}
private:
Obj & obj_;
};
class A {
public:
int Add3(int x) const {
std::cout << "Add3(" << x << ")" << std::endl;
return x + 3;
}
template <typename T>
auto AddXY(int x, T y) {
std::cout << "AddXY(" << x << ", " << y << ")" << std::endl;
return x + y;
}
};
class B {
public:
template <typename V>
std::string ToStr() {
std::cout << "ToStr(" << V{}() << ")" << std::endl;
return "B_ToStr " + std::to_string(V{}());
}
void VoidFunc(int x, bool a) {
std::cout << "VoidFunc(" << x << ", " << std::boolalpha << a << ")" << std::endl;
}
};
#define SHOW_EX(code) \
try { code } catch (std::exception const & ex) { \
std::cout << "\nException: " << ex.what() << std::endl; }
int main() {
try {
using MR = MethodsRunner<
Cmd<1, &A::Add3>,
Cmd<2, &A::AddXY<long long>>,
Cmd<3, &B::ToStr<std::integral_constant<int, 17>>>,
Cmd<4, &B::VoidFunc>
>;
auto VarInfo = [](auto const & var) {
std::cout
<< ", var_idx: " << var.index()
<< ", var_type: " << std::visit([](auto const & x){
return TypeName<decltype(x)>();
}, var)
<< ", var: " << TypeName<decltype(var)>()
<< std::endl;
};
A a;
B b;
{
auto var = MR::Run(1, a, 5);
std::cout << "cmd 1: var_val: " << std::get<int>(var);
VarInfo(var);
}
{
auto var = MR::Run(2, a, 3, 7);
std::cout << "cmd 2: var_val: " << std::get<long long>(var);
VarInfo(var);
}
{
auto var = MR::Run(3, b);
std::cout << "cmd 3: var_val: " << std::get<std::string>(var);
VarInfo(var);
}
{
auto var = MR::Run(4, b, 12, true);
std::cout << "cmd 4: var_val: VoidT";
std::get<VoidT>(var);
VarInfo(var);
}
std::cout << "------ Single object runs: ------" << std::endl;
SingleObjectRunner<MR, A> ar(a);
ar(1, 5);
ar(2, 3, 7);
SingleObjectRunner<MR, B> br(b);
br(3);
br(4, 12, true);
std::cout << "------ Runs with exceptions: ------" << std::endl;
SHOW_EX({
auto var = MR::Run(4, b, false);
});
SHOW_EX({
auto var = MR::Run(5, b);
});
return 0;
} catch (std::exception const & ex) {
std::cout << "Exception: " << ex.what() << std::endl;
return -1;
}
}
输出:
Add3(5)
cmd 1: var_val: 8, var_idx: 0, var_type: int, var: std::variant<int, NotCallable>
AddXY(3, 7)
cmd 2: var_val: 10, var_idx: 1, var_type: long long, var: std::variant<NotCallable, long long>
ToStr(17)
cmd 3: var_val: B_ToStr 17, var_idx: 1, var_type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, var: std::variant<NotCallable, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >
VoidFunc(12, true)
cmd 4: var_val: VoidT, var_idx: 1, var_type: VoidT, var: std::variant<NotCallable, VoidT>
------ Single object runs: ------
Add3(5)
AddXY(3, 7)
ToStr(17)
VoidFunc(12, true)
------ Runs with exceptions: ------
Exception: Calling method 'void (B::*)(int, bool)' with wrong object type and/or wrong argument types or count and/or wrong template arguments! Object type 'B', tuple of arguments types 'std::tuple<bool>'.
Exception: Unknown command 5! Number of commands 4
注意:在我的代码中,我使用#include <cxxabi.h>
来实现 TypeName<T>()
函数,此头文件仅用于名称重整的目的。此头文件在MSVC编译器中不可用,并且在Windows版本的CLang中可能不可用。在MSVC中,您可以删除#include <cxxabi.h>
,并在 TypeName<T>()
中不进行重整,只需返回 return typeid(T).name();
。这个头文件是我代码中唯一的非交叉编译的部分,如果需要,您可以轻松移除对该头文件的使用。
switch
表来调用具体的函数,没有其他多余的东西。 - Arty