类Server
,有函数成员reg()
和call()
,我想要的效果是,reg()
接收一个整数id
和一个函数对象,call()
可根据id
使用传入的参数调用对应的函数对象。就像下面这样:
注:register
是关键字,所以用reg
代替。
尝试1 粉饰的美好
由“指定的编号”和“值”,通过编号访问值,想到可以用std::unordered_map
储存编号和函数。
由于要储存的函数可能是 lambda 函数,可能是普通函数,也可能是函数对象,为了统一,想到使用std::function
。
但std::function
是模板,函数的返回值和各参数类型是模板参数,所以不能直接在std::unordered_map
里存std::function
,想到可以存std::any
,在用的时候再用std::any_cast
转换为std::function
(用传入的参数进行CTAD,类模板参数推导)。
但这样的话,函数的返回值推导不出来,所以妥协一下:作为参数传给register()
的函数,返回值统一为int
,表示函数调用是否成功;返回值通过非 const 引用参数传递。
最后得到的类Server
是这样的:
测试一下:
完整代码:try 01 store functions with different signatures in a container (github.com)
看起来不错嘛,但如果再仔细点去看,可能会对这个 lambda 函数感到奇怪:
为什么前两个参数的类型用std::string
而不是用const std::string&
或std::string_view
?
以及,这句代码:
为什么要写成std::string{"hello"}
这样呢,直接写"hello"
不行吗?
还有这里:
为什么要用1, 2.0, 3.0
,直接写1, 2, 3
不行吗?
确实是不行,确实非得这样写。但实在是有苦衷啊,“非不为也,实不能也”。因为这里:
所调用的函数的签名是根据调用函数时传入的参数来推导的,所以如果我写1, 2, 3
而不是1, 2.0, 3.0
,那么参数类型就会被推断为int, int, int
,而实际上函数的参数类型是int, double, double
,这样any_cast()
就会失败,就会抛出异常。
如上所述,该方案不可取。
尝试2
[[2024-07-05]] 11:10
上面的方案之所以不可取,是因为在把std::function
存入std::any
时丢失了函数返回值类型和参数类型这些信息,只能将调用函数时传入的参数(arguments)的类型作为函数参数(parameters)类型。
而之所以要把std::function
存入std::any
,是因为std::unordered_map
的值类型需要是统一的。具有不同模板参数的std::function
是不同的类型。
如果有一种数据结构,可以直接存不同类型的值就好了。
std::tuple
就是这样一种数据结构。测试如下:
类Server
当前如下:
测试如下:
跟最开始的设想稍微有些不同。最开始想着可以使用成员函数reg()
在运行时动态添加可以调用的函数,现在需要在构造Server
实例时传入由std::function
组成的std::tuple
。最开始想着,调用函数时将函数编号作为函数call()
的参数之一传入,现在需要将函数编号作为函数模板call()
的模板参数传入。
但想要的效果实现了,不是吗。