【发布时间】:2021-05-21 14:50:30
【问题描述】:
我正在寻找一种方法来设置和调用具有任意参数和返回类型的函数。一个用例是高级脚本。像这样的:
// universal function
using dynfunction = std::any (*)(std::vector<std::any> args);
我做了一个简化的例子:
#include <any>
#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std::string_literals;
using namespace std::string_view_literals;
class MyClass {
public:
int foo(double d, std::string m) {
std::cout << "foo " << d << ", " << m << std::endl;
return 42;
}
virtual double bar(int i) {
return -i;
}
};
class MyDerivedClass : public MyClass {
virtual double bar(int i) override {
return -i*3.1412;
}
};
void foobar(char c) {
std::cout << "foobar " << c << std::endl;
}
// universal function
using dynfunction = std::any (*)(std::vector<std::any> args);
// caller wrappers
std::any call_foo(std::vector<std::any> args) {
return std::any_cast<MyClass*>(args[0])->foo(std::any_cast<double>(args[1]), std::any_cast<std::string>(args[2]));
}
std::any call_bar(std::vector<std::any> args) {
return std::any_cast<MyClass*>(args[0])->bar(std::any_cast<int>(args[1]));
}
std::any call_foobar(std::vector<std::any> args) {
foobar(std::any_cast<char>(args[0]));
return {}; // void
}
// demonstrate dynamic resolution
std::map<const std::string_view, const dynfunction> functions = {
{ "foo"sv, call_foo },
{ "bar"sv, call_bar },
{ "foobar"sv, call_foobar }
};
int main() {
MyClass obj;
std::any ret = functions["foo"sv](std::vector<std::any>{&obj, 7.0, "Hello World!"s});
ret = functions["bar"sv](std::vector<std::any>{&obj, ret});
std::cout << "obj.bar returned " << std::any_cast<double>(ret) << std::endl;
MyDerivedClass obj2;
ret = functions["bar"sv](std::vector<std::any>{dynamic_cast<MyClass*>(&obj2), 11}); // derived class must be cast to base
std::cout << "obj2.bar returned " << std::any_cast<double>(ret) << std::endl;
functions["foobar"sv](std::vector<std::any>{'x'});
}
这可行,但我想知道是否有更简单或更直接的方法。
另外,困扰我的一件事是需要将多态类型(即派生类的对象)转换为正确的基类才能正常工作(参见示例中的obj2)。有没有办法解决这个问题?
注意:示例是故意简化的(使用std::vector 作为参数,std::map 用于查找,这些只是概念上的替代品)。
编辑:似乎需要更多信息。
这是“模型驱动架构”(MDA) 的一部分(不是 OMG 变体 - 我们的解决方案比它早了大约 6 年)。我们有自己的 OOP/4GL 语言“V”,我们在 1995 年创建。它在运行时保留所有元信息。这使我们能够动态生成所有 GUI、数据库设计、数据绑定、脚本界面。在其他语言中,这称为“反射”,但与我们能做的相比,Java&Co 中可用的功能非常有限。
MDA 意味着(除其他外)我们需要一种方法将控制从解决方案的动态“模型驱动”部分转移到实际应用程序逻辑(即功能)。我们拥有的内置脚本语言只是众多用例之一,我认为它对于普通观众来说是最容易理解的。
原始数据类型的数量是有限的,而多态数据类型却有数千种。 std::any 似乎比 std::variant 更优雅,因为 a) 它有这种很酷但完全透明的堆优化,(即小型数据类型不需要动态分配)和 b) 我们可以保持数据类型之间的关注点分离不变,即所有如果我们添加新的数据类型,std::any 处理代码可以保持不变。但如果std::variant 有重要的优势,我愿意考虑。
【问题讨论】:
-
如果您尝试在实际的实际用例中使用这种方法,您可能会发现它没有用。在调用函数之前,您需要已经知道正确的参数类型,否则
any_cast会失败。返回类型也有类似的问题,你需要知道std::any会持有什么类型。所以隐藏这些信息并没有任何好处。 -
如果您尝试将基于宏的系统转换为 C++,那么使用模板的非
std::any解决方案会更好。编辑:类型双关语的好处是允许统一对待各种类型,但你不能用std::any做到这一点,因为你需要关于实际类型的外部信息。 -
@markus 您在评论中提供了清晰的说明——您正在尝试对现有的基于宏的解决方案进行现代化改造。我们看不到这个宏解决方案的作用,但不太可能
std::any与您的宏正在做的现代等效。 -
请给我们看一个用例,这样我们就可以在同一个页面上。我现在想不出一个。
-
@markus 它可能有助于edit 你的问题包括所有相关细节。似乎不清楚您的 cmets 是否属于问题的一部分。
标签: c++ c++20 std-function stdany