简体   繁体   中英

What's the “right” way to type-erase a member function pointer?

I am writing a test fixture which involves ensuring certain callbacks are called at appropriate times (actually Qt signals, but it shouldn't matter for the sake of my problem). To help with this, I created a helper class that records when a callback (signal) fires into a list.

This list needs to be able to record which callback (signal) fired. I would also prefer to not need to create a new enumeration specifically for this purpose. My idea was to instead record the address of the signal as a type-erased pointer so I can check the record against the address of the signal.

To make things a little easier on myself, I record the signal type as a:

template <typename Object>
class SignalType
{
public:
  SignalType() = default;
  SignalType(SignalType const&) = default;
  SignalType(SignalType&&) = default;

  template <typename R, typename... Args>
  SignalType(R (Object::*member)(Args...))
    : member{reinterpret_cast<void (Object::*)()>(member)} {}

  template <typename R, typename... Args>
  bool operator==(R (Object::*other)(Args...)) const
  { return this->member == reinterpret_cast<void (Object::*)()>(other); }

private:
  void (Object::*member)() = nullptr;
};

This "hides" the type erasure from the point of use, so I can later just write:

QCOMPARE(event.type, &SomeObject::someMethod);

...without needing to clutter that with a cast.

However, GCC is unhappy:

warning: cast between incompatible pointer to member types from ‘void (SomeObject::*)(...)’ to ‘void (SomeObject::*)()’ [-Wcast-function-type]

Is there a way to make GCC happy without resorting to diagnostic #pragma s to simply shut up the warning? Is there some other, "better" way to achieve this particular flavor of type-erasure? (Note that I don't need to ever call the member that SignalType encapsulates; I just need to be able to test for equality.)


Sigh. Should search on the warning message, not what I'm trying to do. Technically I guess this is a duplicate of Cast Between Incompatible Function Types in gcc , however that only asks how to get rid of the warning, and isn't clear what the code is trying to accomplish. So, in order that I might learn something useful here, please focus on if there is some other, "cleaner" way to accomplish my goal rather than just closing this as a duplicate and saying "it can't be fixed".

Here is a solution that holds a function pointer that can compare two values of the same type whilst also acting as a std::type_info checking at run time if two types are the same. It stores the function pointer in a char[] .

#include <new>

template<typename Object>
class SignalType
{
public:
  SignalType() = default;
  SignalType(SignalType const&) = default;
  SignalType& operator=(SignalType const&) = default;

  template<typename R, typename... Args>
  SignalType(R (Object::*member)(Args...)) noexcept
    : comparator(&compare_members_from_void_ptr<R, Args...>) {
    using member_ptr_type = R(Object::*)(Args...);
    static_assert(sizeof(member_ptr_type) <= sizeof(void(Object::*)()), "Member pointer type too large?");
    static_assert(alignof(member_ptr_type) <= alignof(void(Object::*)()), "Member pointer align too large?");
    // Don't need to destruct since it has a trivial destructor
    new (member_storage) member_ptr_type(member);
  }

  bool operator==(const SignalType& other) const {
    if (!comparator) return !other.comparator;  // Check both empty
    // Same comparator implies same type
    return comparator == other.comparator && comparator(member_storage, other.member_storage);
  }
  bool operator!=(const SignalType& other) const {
    return !(*this == other);
  }

   // Return true if these contain pointers to members of the same type
  bool is_same_type_as(const SignalType& other) const {
    return comparator == other.comparator;
  }

  // true if holding a typed pointer (could still be nullptr)
  explicit operator bool() const {
    return comparator;
  }

  // Check if holding an `R(Object::*)(Args...)`
  template<typename R, typename... Args>
  bool is_type() const noexcept {
    return comparator && comparator == &compare_members_from_void_ptr<R, Args...>;
  }

  // Returns the held function pointer if it is of type R(Object::*)(Args...), else nullptr
  template<typename R, typename... Args>
  R(Object::* get() const noexcept)(Args...) {
    return is_type<R, Args...>() ? *static_cast<R(Object::**)(Args...)>(static_cast<void*>(member_storage)) : nullptr;
  }
private:
  alignas(void(Object::*)()) char member_storage[sizeof(void(Object::*)())];
  bool (*comparator)(const void*, const void*) = nullptr;

  template<typename R, typename... Args>
  static bool compare_members_from_void_ptr(const void* a, const void* b) noexcept {
    return *static_cast<R(Object::*const *)(Args...)>(a) == *static_cast<R(Object::*const *)(Args...)>(b);
  }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM