这种事情可以通过一些工作来实现。首先,重要的是要理解为什么更简单的事情是不可能的:在 C/C++ 中,将参数传递给函数的确切机制以及如何从函数获取返回值取决于参数的类型(和大小)。这是在应用程序二进制接口 (ABI) 中定义的,它是一组允许由不同编译器编译的 C++ 代码进行互操作的约定。该语言还指定了在调用站点发生的一系列隐式类型转换。因此,简短而简单的答案是,在 C/C++ 中,编译器无法发出机器代码来调用在编译时签名未知的函数。
现在,您当然可以在 C++ 中实现 Javascript 或 Python 之类的东西,其中所有值(与这些函数相关)都是动态类型的。你可以有一个基本的“值”类,它可以是整数、浮点数、字符串、元组、列表、映射等。你可以使用std::variant
,但在我看来,这实际上在语法上很麻烦,你最好自己做:
enum class Type {integer, real, str, tuple, map};
struct Value
{
// Returns the type of this value.
virtual Type type() const = 0;
// Put any generic interfaces you want to have across all Value types here.
};
struct Integer: Value
{
int value;
Type type() const override { return Type::integer; }
};
struct String: Value
{
std::string value;
Type type() const override { return Type::str; }
};
struct Tuple: Value
{
std::vector<Value*> value;
Type type() const override { return Type::tuple; };
}
// etc. for whatever types are interesting to you.
现在,您可以将函数定义为任何接受 singleValue*
并返回 single的东西Value*
。多个输入或输出参数可以作为元组或映射传入:
using Function = Value* (*)(Value*);
您的所有函数实现都需要获取类型并使用参数做一些适当的事情:
Value* increment(Value* x)
{
switch (x->type())
{
Type::integer:
return new Integer(((Integer*) x)->value + 1);
Type::real:
return new Real(((Real*) x)->value + 1.0);
default:
throw TypeError("expected an integer or real argument.")
}
}
increment
现在与Function
类型兼容,可以存储在mFuncs
. 您现在可以在未知类型的参数上调用未知类型的函数,如果参数不匹配,您将得到异常,或者如果参数兼容,则会得到某种未知类型的结果。
您很可能希望将函数签名存储为您可以自省的内容,即动态计算 aFunction
采用的参数的数量和类型。在这种情况下,您可以Function
使用必要的自省函数创建一个基类,并为其提供一个operator ()
以使其看起来像调用常规函数一样。然后,您将根据需要派生和实施Function
。
这是一个草图,但希望包含足够的指针来指明方向。还有更多类型安全的方法来编写此代码(当我已经检查过类型时,我喜欢 C 风格的强制转换,但有些人可能坚持你应该dynamic_cast
改用),但我认为这不是这个问题的重点. 您还必须弄清楚如何管理 Value* 对象的生命周期,这是一个完全不同的讨论。