存储指向成员函数的指针对clang ++不起作用

时间:2017-03-29 18:03:21

标签: c++ c++11 g++ 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函数,因为代码在裸机上运行

2 个答案:

答案 0 :(得分:0)

显然在g ++中,它允许你使用0参数转换成函数指针的成员函数的指针,并且调用它将导致使用损坏的this指针调用该成员函数。

此外,表达式b.*m,其中m是一个成员函数而b是一个类将评估虚拟调度,然后可以将生成的对象强制转换为函数指针已经评估了虚拟调度。它仍然会有一个损坏的this值。

这两项都是该标准下的非法行动;程序未定义,或使用它是未定义的行为。

在g ++中,this指针是垃圾的事实是一个很好的理由,为什么这不是一个非常有用的策略。 Here是一个用B中的某个状态构建它的例子,它不是在clang ++上编译而是在g ++上进行segfaulting。

成员函数和指向对象的指针是两件事。无论你如何破解它们,将它们存储在一个东西(一个函数指针)都不会起作用。

有很多方法可以解决这个问题。最简单的方法是使用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。

But it works

fun<Sig>std::function的基本变体,几乎没有状态。丢失的SFINAE需要测试Lambda是否与签名兼容不等于fun类型本身。

这些物体的大小约为4个;您可以配置Sz参数的大小。 2可能足以适合成员函数和this指针。我四舍五入到了。

这些东西也可以存储指向函数的指针。

他们可以存储指向lambdas或fun变体的指针,但不会延长所述lambda的生命周期。这很危险,但通常很有用。

std::function类似,它们不需要签名完全匹配。与C ++ 14的std::function不同,如果Rvoid,则存储的对象也必须返回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;}
};

live example

以上内容符合标准,不依赖于std。编写SFINAE以确保在恶劣情况下不会调用Lambda&&重载可能需要重写std中的某些内容,例如std::is_sameenable_if

答案 1 :(得分:0)

您滥用g ++中的Bound member functions扩展名。您正在转换为错误类型的函数指针,该指针具有未定义的行为:

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 bug说您滥用扩展名应该是错误。