Joel
Joel

Reputation: 1145

Can I detect if a member function exists and is accessible to a friend in C++?

I am trying to take the ideas from Is it possible to write a template to check for a function's existence? and apply them, but I am running into some trouble. I have the following defined:

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {\
        typedef char yes[1];                                            \
        typedef char no[2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func> *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

// Creates a member function detector and two overloads of a message handler
// registration function, one that registers a member function of a specific name
// if it exists and another that does nothing if that member function doesn't exist.

#define DEFINE_REGISTER_FUNC(msgid, msgtype, handlername) \
    HAS_MEM_FUNC(handlername, has_##handlername);\
    template <typename T>\
    static void register_##handlername(\
        T* pWnd, \
        typename std::enable_if<has_##handlername<T, void(T::*)(msgtype&)>::value, T>::type* t = nullptr) \
    {\
        (void) t;\
        pWnd->setMessageHandler(\
            msgid,\
            Handler<msgtype>(std::bind(&T::handlername, pWnd, std::placeholders::_1)));\
    }\
    \
    template <typename T> \
    static void register_##handlername(\
        T* pWnd, \
        typename std::enable_if<!has_##handlername<T, void(T::*)(msgtype&)>::value, T>::type* t = nullptr) \
    {\
        (void)pWnd;\
        (void)t;\
    }

I also have a class defined like this:

template <typename T>
class RegisterHandlers
{
public:
    template <typename T>
    static void registerHandlers(T* pWnd)
    {
        register_onCreate(pWnd);
        register_onPaint(pWnd);
    }

private:
    DEFINE_REGISTER_FUNC(WM_CREATE, CreateMessage, onCreate);
    DEFINE_REGISTER_FUNC(WM_PAINT, PaintMessage, onPaint);
};

The idea is that if the class T has a member function onCreate(CreateMessage&), it will automatically be registered as the handler for WM_CREATE. If it has no such method, then the do-nothing overload of register_onCreate will be called and the compiler will be happy. Same for the onPaint handler (I included that mostly to illustrate that the SFINAE does work).

This works fine if I have a class like this:

class MainWnd
{
public:
    friend class RegisterHandlers<MainWnd>;

    MainWnd()
    {
        RegisterHandlers<MainWnd>::registerHandlers(this);
    }

protected:
    void onCreate(CreateMessage&) { /* do some stuff */ }
};

However, the moment I add this:

class SubWnd : public MainWnd
{
public:
    friend class RegisterHandlers<SubWnd>;

    SubWnd()
    {
        RegisterHandlers<SubWnd>::registerHandlers(this);
    }

protected:
    void onPaint(PaintMessage&) { /* do some stuff */ }
};

I get an error that MainWnd::onCreate is inaccessible (cannot access protected member). I broke down the macros to help find where the problem is actually occurring, and it seems to be in the HAS_MEM_FUNC macro, probably at this line:

template <typename _1> static yes &chk(type_check<Sign, &_1::func> *);

So, I have two questions really:

  1. Is it possible to do what I am trying to do without making the event handlers public?
  2. Is it also possible to avoid re-registering event handlers if the base class already did it? Phrased another way, is there a way to tell if the function was declared in the base class or if it originated in the derived class?

In case it's relevant, I am trying to do this on the Visual Studio 2013 Preview compiler. The motivation for this question is that I am experimenting with C++11 features and Windows API wrappers, and I am trying to see if there are better solutions than large vtables and message map macros for routing messages to an appropriate handler.

Upvotes: 2

Views: 420

Answers (1)

Mike Kinghan
Mike Kinghan

Reputation: 61575

The obstacle here is this:

Class SubWnd, derived from MinWnd, declares friendship:

friend class RegisterHandlers<SubWnd>;

so that RegisterHandlers<SubWnd>::registerHandlers(this) can do the registering of its own protected handler onPaint and also - redundantly - of the protected handler onCreate that it inherits from MainWnd. The latter registration is redundant because the base class MainWnd's constructor will already have registered onCreate. RegisterHandlers<T>::registerHandlers(WinType *) is always going to "register" all the handlers that any window might have, doing a no-op if WinType doesn't actually have a given handler and repeating the registration if it has already been done by a base class constructor.

But to register onCreate, redundantly or not, which is a protected member of MainWnd, RegisterHandlers<SubWnd> needs to be a friend of MainWnd. And it isn't: it is only a friend of SubWnd.

You cannnot get rid of the template parameterization of RegisterHandlers<T> by window-type T because you depend on that template parameter for your SFINAE method-introspecting

But this SFINAE method-introspecting, I have to say, is a futile palaver in the present case. Every one of your window-class constructors still has to do something to get its own handlers registered. The way that you want it to go about this is by delegating the task to RegisterHandlers<T>::registerHandlers(WinType *), which will:

  • Attempt to register all handlers any window could have,
  • Doing nothing whenever WinType doesn't have the handler,
  • Redundantly re-registering the handler when WinType's base class constructor has already registered it, and
  • Non-redundantly and successfully registering the specific handlers of WinType.

The only part of all this that each window type actually, but unavoidably, needs to do is the last. So to your two questions:

  1. Is it possible to do what I am trying to do without making the event handlers public?

Yes. Just have each window-type register its own handlers.

  1. Is it also possible to avoid re-registering event handlers if the base class already did it?

Yes. Same answer.

I appreciate that you are merely experimenting, but this experiment is not promising.

You attached a third question to question 2:

Is there a way to tell if the function was declared in the base class or if it originated in the derived class

Yes. If you consult again this answer, and then also consult my answer to the same question just below, you will see that the first solution detects a member function T::mf when mf is defined in T but not when mf is inherited by T, whereas my solution detects mf in either case. Thus you can SFINAE-probe for T::mf by both methods and will know that mf is inherited if the first method does not detect it and the second method does.

Upvotes: 1

Related Questions