C++是否可以实现指向不同参数列表的函数指针

Is it possible for C++ to implement function pointers point to different parameter lists?

本文关键字:参数 列表 指针 函数 是否 实现 C++      更新时间:2023-10-16

我最近写了一篇关于类成员函数回调函数的文章。我需要保存回调对象和函数指针,然后调用函数指针,并在需要回调的地方填写适当的参数。

我一开始是typedef void (AAA::*Function)(int a, int b);的一种形式,但当我需要支持成员函数的不同参数列表时,我显然需要一种动态的方式来实现它

class AAA
{
public:
int add(int a, int b)
{
return (a + b);
}
};
class BBB
{
public:
void setValue(std::string value)
{
this->value = value;
}
private:
std::string value;
};
class CCC
{
public:
void bind(??? p)    // Binding objects and callback functions.
{
this->p = p;
}
template <class... Args>
auto callback(Args&&... args)   // Autofill parameter list.
{
return this->p(std::forward<Args>(args)...);
}
private:
??? p;  // How is this function pointer implemented?
};
int main()
{
AAA aaa;
BBB bbb;
CCC ccc;
ccc.bind(???(aaa, &AAA::add));
int number = ccc.callback(5, 6);
ccc.bind(???(bbb, &BBB::setValue));
ccc.callback("Hello");
system("pause");
return 0;
}

我不知道如何实现函数指针"???"。

您基本上要求拥有完全动态类型化和检查过的函数调用。

要进行完全动态的函数调用,基本上必须抛出C++函数调用系统。

这是个坏主意,但我会告诉你怎么做。

一个动态可调用对象大致如下:

using dynamic_function = std::function< std::any( std::vector<std::any> ) >

在哪里使用

struct nothing_t {};

当我们想要返回CCD_ 2时。

然后,您编写一个机器,它接受一个对象和一个特定的签名,并将其封装起来。

template<class R, class...Args, class F>
struct dynamic_function_maker {
template<std::size_t...Is>
dynamic_function operator()(std::index_sequence<Is...>, F&& f)const {
return [f=std::forward<F>(f)](std::vector<std::any> args)->std::any {
if (sizeof...(Is) != args.size())
throw std::invalid_argument("Wrong number of arguments");
if constexpr( std::is_same< std::invoke_result_t<F const&, Args... >, void >{} )
{
f( std::any_cast<Args>(args[Is])... );
return nothing_t{};
}
else
{
return f( std::any_cast<Args>(args[Is])... );
}
};
}
dynamic_function operator()(F&& f)const {
return (*this)(std::make_index_sequence<sizeof...(Args)>{}, std::forward<F>(f));
}
};
template<class R, class...Args, class F>
dynamic_function make_dynamic_function(F f){
return dynamic_function_maker<R,Args...,F>{}(std::forward<F>(f));
}

接下来,您将需要推导函数指针等的签名:

template<class R, class...Args>
dynamic_function make_dynamic_function(R(*f)(Args...)){
return dynamic_function_maker<R,Args...,F>{}(std::forward<F>(f));
}
template<class Tclass R, class...Args>
dynamic_function make_dynamic_function(T* t, R(T::*f)(Args...)){
return dynamic_function_maker<R,Args...,F>{}(
[t,f](auto&&...args)->decltype(auto){return (t->*f)(decltype(args)(args)...);}
);
}

然后在修复了上面的拼写错误之后,你应该能够解决你原来的问题。

同样,作为一个能够真正编写和理解上述代码的人,我强烈建议您不要使用它。它是脆弱和危险的。

几乎从来没有一个好的理由将回调存储在你不知道要用什么参数调用它的地方。

对于要调用它的每一组参数,都应该有不同类型和实例的CCC。当人们问这个问题时,他们问错了99/100次。

C++是一种类型安全的语言。这意味着你不能完全按照你在问题中概述的去做。指向采用特定参数的函数的指针与指向采用不同参数的函数指针的类型不同。这是C++的基础。

std::bind可以用于将不同类型的类型擦除为同一类型,但最后会得到一个类型,该类型只能使用匹配的参数集(如果有的话)调用。不可能调用带有实际参数的"底层"绑定函数。这是因为std::bind的全部目的是让它们消失,无法访问。这就是std::bind的作用。

在保持C++类型安全的边界和约束的情况下,您只有有限的一组选项来实现这一点。

  1. 以某种方式使用void *。事实上,不要。不要那样做。这只会引起更多的问题和头痛。

  2. 有一个单独的回调列表和类,每个列表对应一组采用特定参数的回调。在调用回调时,您必须知道要传递什么参数。因此,只需从适当的列表中获取回调即可。

  3. 使用std::variant。类型安全的std::variant仅为C++17(但boost有一个类似的模板,基本上是等效的,可用于较旧的C++版本)。所有回调都接受一个std::variant参数,一个可能的参数集的变体(指定为其中的std::tuple,或某个类/结构实例)。如果每个回调接收到包含错误参数值的std::variant,则必须决定该怎么办。

或者,std::variant可以是不同std::function类型的变体,从而将类型检查的责任转移到调用者身上,而不是每次回调。

归根结底,C++从根本上说是一种类型安全的语言;这正是人们选择使用C++而不是不具有相同类型安全性的不同语言的原因之一。

但作为一种类型安全的语言,这意味着在处理不同类型时有一定的局限性。具体来说:你不能。C++中的所有内容始终是,而且必须是单一类型。