Question

I wrote a class to store either a function pointer or a member function pointer (not both at a time). When I store the member function pointer, I store an object pointer too (the receiver).

The problem is: I don't know in advance neither the object's type nor the function signature, so I use a template typename. For arguments I use variadic templates.

I have a code similar to this:

template <typename... Args>
class A
{
public:
    template <typename Object, typename Ret>
    A (Object *receiver, Ret (Object::*mem)(Args...)); // store the member function pointer

    template <typename Ret>
    A (Ret (*function)(Args...)); // store the global function pointer

    void operator()(Args... args) const; // to call the function pointer
    // ... more public stuff ...

private:
    class UndefinedClass;
    typedef void (Undefined::*MemFunPtrType)(Args...)

    union
    {
        struct
        {
            MemFunPtrType memFunPtr; // the stored member function
            void *receiverPtr; // the receiver object
            void (*call)(void *, MemFunPtrType, Args...);
        } member;

        void (*funPtr)(Args...); // the stored global function
    };

    enum class Type {Function, Member} type;
};

Since a need just one, global function or member function, I put everything inside union.

In the constructor I cast the member function mem to a void (Undefined::*)(Args...) and store it. I took this trick from implementation of std::function.

Using a lambda with no capture, I cast again to the original type, both the object and the function and I call them:

typedef Ret (Object::*OriginalMemberType)(Args...);
// ...
member.call = [] (void *receiver, MemFunPtrType memFun, Args... args)
{
    (static_cast<Object*>(receiver)->*reinterpret_cast<OriginalMemberType>(memFun))(args...);
}

I store this lambda in the call function pointer to call it inside operator(). With an if-else sentence I compare the type data, and I call the correct pointer.

I know this is a little annoying, but it works. I have a lot of tests and all of them pass. But I am worried about the strict aliasing thing. I do a lot of pointer casting and I am not sure if this falls unto undefined behaviour.

Question: Is it allowed to cast from Ret (Object::*)(Args...) to void (UndefinedClass::*)(Args...)? (Ret, Object and Args are template arguments) Note I never call the object without cast again to the original type. It is for storing only.

Question:: Must I compile with -fno-strict-aliasing? If I must, and sice this is a template class, should I put -fno-strict-aliasing in every project using this class? Maybe I could use an array of char of length sizeof(void (UndefinedClass::*)(Args...)) or something like that instead.

Notes:

  1. I use gcc 4.8.1 in Archlinux. I use C++11.
  2. I did not use std::function here just for the thing of using placeholders with std::bind (I don't know the number of placeholders required). And this is a good practice and learning after all. If you know how to do this with std::function, answer is very welcome.
  3. Actually, I use this class in a std::vector for calling many "callbacks" of the same signature (a signal/slot kind framework).

Thank you so much. I will clarify any aspect of this mess, if is required :)

Was it helpful?

Solution

As long your not accessing the address with that wrong pointer type. You are not breaking the rule. so this is not a problem. You are just breaking the rule, if you access it by a bad punned pointer to another type that may be treat similar but is not the same. Maybe you have used the FreeBSD sockets allready, they are throwing in the function bind() also an strict aliasing warning, because your casting sockadr_in to sockadr and inside it gets casted back to the implementation corresponding type. The Warning says it could break the rule, but the implementation prevents it from doing so. As is nice described in this Question: Berkley Sockets, breaking aliasing rules?

And if you even aren't sure about that you will be going to, you could also do a cast to char*. Because (from c99 and i guess it should be same in c++ standards) char* is allowed to allias anything. So as you are in c++ you can even handle with templates casts from different types to char and back inside the function. (Ok the char* cast isn't a good idea if your functions aren't for itnern use only.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top