存储指向成员函数的指针不适用于 CLak++
storing pointer to member function does not work for clang++
当我使用 clang++ 时,我只能调用指向成员的指针函数,我无法强制转换它或将其分配给变量以在需要时调用 i(我想将此变量存储到函数数组中),但使用 G++ 我可以做到这一点,
例如class Base {
public:
typedef void (Base::*A)();
virtual void some_func() = 0;
};
class B: public Base {
public:
void some_func() {
return
}
};
int main() {
B b;
auto h = (Base::A)&Base::some_func;
typedef void (*my_function)();
auto some_func = (my_function)(b.*h);
some_func();
return 0;
}
使用G++ 可以编译和运行,但使用 CLANG++,我得到reference to non-static member function must be called; did you mean to call it with no arguments?
(请注意,我不能在我的代码中使用任何 std::x 函数,因为代码在裸机上运行
你在 g++ 中误用了绑定成员函数扩展。您正在强制转换为错误类型的函数指针,该指针具有未定义的行为:
typedef void (*my_function)();
auto some_func = (my_function)(b.*h);
some_func();
(my_function)(b.*h)
表达式解析虚拟调度以确定(b.*h)()
将调用哪个覆盖函数,但要通过返回的指针实际调用该函数,您仍然需要提供指向对象的this
指针。所以my_function
应该接受一个类型为B*
的参数,并且在调用该函数指针时应该提供一个指向B
的指针。
要正确使用该扩展,您应该执行以下操作:
typedef void (*my_function)(B*);
auto some_func = (my_function)(b.*h);
some_func(&b);
这是一个非标准的GNU扩展,不受Clang支持。据我所知,没有办法让Clang接受这段代码。
我报告了一个 GCC 错误,说你滥用扩展应该是一个错误。
显然在 g++ 中,它允许您将指向成员函数的指针转换为函数指针,并且调用它将导致使用损坏/丢失的this
指针调用该成员函数。
此外,表达式b.*m
,其中m
是成员函数,b
是一个类,将评估虚拟调度,然后可以将生成的对象强制转换为已计算该虚拟调度的函数指针。 它仍将具有损坏的this
值。
这两者都是标准的非法操作;程序要么定义不明确,要么使用它是不定义的行为。
在 g++ 中,this
指针是垃圾这一事实是一个很好的理由,为什么这不是一个非常有用的策略。 下面是一个示例,它以B
中的某种状态构建,并且不在 clang++ 上编译并在 g++ 上编译段错误。
成员函数和指向对象的指针是两件事。 将它们都存储在一个东西(函数指针)中是行不通的,无论你如何破解它。
有许多方法可以解决这个问题。 最简单的是使用std::function
,但禁止您使用标准库。
下一个最简单的方法是编写自己的std::function
变体。 我已经做到了 - 一个"委托",它支持存储指向lambda的指针,指向函数的指针(指向对象的指针+指向成员函数的指针),这些指针与给定签名兼容。
所有这些都是微不足道的"普通旧数据",因此您可以创建一个简单的缓冲区,将它们的值复制到缓冲区中,并保存一个知道如何获取缓冲区指针并调用它的函数。
template<class Sig, unsigned Sz=3*sizeof(void*)>
struct fun;
template<class R, class...Args, unsigned Sz>
struct fun<R(Args...), Sz> {
using invoker_t = R(*)(void*, Args&&...);
invoker_t invoker;
unsigned char buffer[Sz];
template<class D>
struct invoker_base_t {
using invoker_sig = R(*)(void* self, Args&&...args);
static invoker_sig invoker() {
return [](void* self, Args&&...args)->R {
return (*static_cast<D*>(self))(static_cast<Args&&>(args)...);
};
}
};
template<class T, class M>
struct mem_fun_invoker_t:invoker_base_t<mem_fun_invoker_t<T,M>> {
T* t;
M m;
mem_fun_invoker_t(T* tin, M min):t(tin), m(min) {}
mem_fun_invoker_t(mem_fun_invoker_t const&)=default;
R operator()(Args&&...args) {
return (t->*m)(static_cast<Args&&>(args)...);
}
};
template<class T, class M>
static mem_fun_invoker_t<T, M> mem_fun_invoker(T* t, M m){ return {t, m}; }
template<class T>
struct ptr_invoker_t:invoker_base_t<ptr_invoker_t<T>> {
T* t;
ptr_invoker_t(T* tin):t(tin) {}
ptr_invoker_t(ptr_invoker_t const&)=default;
R operator()(Args&&...args) {
return (*t)(static_cast<Args&&>(args)...);
}
};
template<class T>
static ptr_invoker_t<T> ptr_invoker(T* t){ return {t}; }
template<class T, class U, class Ret, class...Ts>
fun(T* t, Ret(U::*mem)(Ts...)) {
auto invoke = mem_fun_invoker( t, mem );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
template<class T, class U, class Ret, class...Ts>
fun(T const* t, Ret(U::*mem)(Ts...)const) {
auto invoke = mem_fun_invoker( t, mem );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
template<class Ret, class...Ts>
fun(Ret(*f)(Ts...)) {
auto invoke = ptr_invoker( f );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
// TODO: SFINAE
template<class Lambda>
fun(Lambda&& lambda) {
auto invoke = ptr_invoker( &lambda );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
R operator()(Args...args) {
return invoker(&buffer, static_cast<Args&&>(args)...);
}
explicit operator bool()const {return invoker;}
};
以上需要质量扫描和更多的断言和一些SFINAE。
但它有效。
fun<Sig>
是几乎没有状态的std::function
的裸骨变体。 缺少的 SFINAE 需要测试Lambda
是否与签名兼容并且不等于fun
类型本身。
这些对象的大小大约为 4 个指针;您可以使用Sz
参数配置大小。 2 可能足以适合成员函数和this
指针。 我四舍五入到 3。
这些东西还可以存储指向函数的指针。
它们可以存储指向 lambda 的指针,或fun
的变体,但不会延长所述 lambda 的生命周期。 这很危险,但通常很有用。
像std::function
一样,它们不需要签名来完全匹配。 与C++14的std::function
不同,如果R
void
,存储的对象也必须返回void
。 解决此问题需要做更多的工作:
namespace details {
template<class Sig, class D>
struct invoker_base_t;
template<class R, class...Args, class D>
struct invoker_base_t<R(Args...), D> {
using invoker_sig = R(*)(void* self, Args&&...args);
static invoker_sig invoker() {
return [](void* self, Args&&...args)->R {
return (*static_cast<D*>(self))(static_cast<Args&&>(args)...);
};
}
};
template<class...Args, class D>
struct invoker_base_t<void(Args...), D> {
using invoker_sig = void(*)(void* self, Args&&...args);
static invoker_sig invoker() {
return [](void* self, Args&&...args)->void {
(*static_cast<D*>(self))(static_cast<Args&&>(args)...);
};
}
};
template<class Sig, class T, class M>
struct mem_fun_invoker_t;
template<class R, class...Args, class T, class M>
struct mem_fun_invoker_t<R(Args...), T, M>:
invoker_base_t<R(Args...), mem_fun_invoker_t<R(Args...),T,M> >
{
T* t;
M m;
mem_fun_invoker_t(T* tin, M min):t(tin), m(min) {}
mem_fun_invoker_t(mem_fun_invoker_t const&)=default;
R operator()(Args&&...args) {
return (t->*m)(static_cast<Args&&>(args)...);
}
};
template<class...Args, class T, class M>
struct mem_fun_invoker_t<void(Args...), T, M>:
invoker_base_t<void(Args...), mem_fun_invoker_t<void(Args...),T,M> >
{
T* t;
M m;
mem_fun_invoker_t(T* tin, M min):t(tin), m(min) {}
mem_fun_invoker_t(mem_fun_invoker_t const&)=default;
void operator()(Args&&...args) {
(t->*m)(static_cast<Args&&>(args)...);
}
};
template<class Sig, class T>
struct ptr_invoker_t;
template<class R, class...Args, class T>
struct ptr_invoker_t<R(Args...), T>:
invoker_base_t<R(Args...), ptr_invoker_t<R(Args...),T> >
{
T* t;
ptr_invoker_t(T* tin):t(tin) {}
ptr_invoker_t(ptr_invoker_t const&)=default;
R operator()(Args&&...args) {
return (*t)(static_cast<Args&&>(args)...);
}
};
template<class...Args, class T>
struct ptr_invoker_t<void(Args...), T>:
invoker_base_t<void(Args...), ptr_invoker_t<void(Args...),T> >
{
T* t;
ptr_invoker_t(T* tin):t(tin) {}
ptr_invoker_t(ptr_invoker_t const&)=default;
void operator()(Args&&...args) {
(*t)(static_cast<Args&&>(args)...);
}
};
}
template<class Sig, unsigned Sz=3*sizeof(void*)>
struct fun;
template<class R, class...Args, unsigned Sz>
struct fun<R(Args...), Sz> {
using invoker_t = R(*)(void*, Args&&...);
invoker_t invoker;
unsigned char buffer[Sz];
template<class T, class M>
static details::mem_fun_invoker_t<R(Args...), T, M> mem_fun_invoker(T* t, M m){ return {t, m}; }
template<class T>
static details::ptr_invoker_t<R(Args...), T> ptr_invoker(T* t){ return {t}; }
template<class T, class U, class Ret, class...Ts>
fun(T* t, Ret(U::*mem)(Ts...)) {
auto invoke = mem_fun_invoker( t, mem );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
template<class T, class U, class Ret, class...Ts>
fun(T const* t, Ret(U::*mem)(Ts...)const) {
auto invoke = mem_fun_invoker( t, mem );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
template<class Ret, class...Ts>
fun(Ret(*f)(Ts...)) {
auto invoke = ptr_invoker( f );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
// TODO: SFINAE
template<class Lambda>
fun(Lambda&& lambda) {
auto invoke = ptr_invoker( &lambda );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
R operator()(Args...args) {
return invoker(&buffer, static_cast<Args&&>(args)...);
}
explicit operator bool()const {return invoker;}
};
活生生的例子。
以上符合标准,不依赖于std
。 编写 SFINAE 以确保在不良情况下不会调用Lambda&&
过载可能需要重写一些std
,例如std::is_same
和enable_if
。
- 为什么指针不写入类的地址?
- 为什么"具有常量成员的结构"类型的指针不能指向"具有非常量成员的结构"?
- 当目标指针不是基类的类型时,为什么允许dynamic_cast为多态类生成 null 指针?
- 比较两个 constexpr 指针不是 constexpr?
- 为什么智能指针不能用通常的指针方式声明
- OpenGL/Glew C++纹理不适用
- 字符指针不递增?
- C++为什么原始指针不会增加shared_ptr的引用计数?
- 为什么在C++中使用delete后指针不为NULL
- 堆指针不会被分配给数组有什么原因吗
- 指向类中函数方法的指针不起作用
- 在 c++ 中返回矢量的指针不起作用
- 仅当指针不是 nullptr 时才调用方法
- 当我在 windows7 中安装程序时,我指定的字体大小不适用
- 班上的Arduino步进电动机指针不起作用
- 功能指针不起作用(C )
- 投射时共享指针不起作用?
- accelerator.cu(8): 错误:属性"managed"在这里不适用?
- 为什么双指针不修改表?
- 指向结构的指针不起作用