I've basically really bad at working on my own projects, but with the recent release of Visual Studio 2017 RC and its improved C++17 support I figured it was time to crack on again...

Simple Changes First

To that end I've spent a bit of time today updating my own basic windowing library to use C++17 features. Some of the things have been simple transforms such as converting typedef to using, others have been more OCD satisfying;

// This ...
namespace winprops
{
    enum winprops_enum
    {
    fullscreen = 0,
    windowed
    };
}
typedef winprops::winprops_enum WindowProperties;

// ... becomes this ...
enum class WindowProperties
{
    fullscreen = 0,
    windowed
};

How Thing Were

The biggest change however, and the one which makes me pretty happy, was in the core message handler which hasn't been really updated since I wrote it back in 2003 or so.

The old loop looked like this;

LRESULT CALLBACK WindowMessageRouter::MsgRouter(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    // attempt to retrieve internal Window handle
    WinHnd wnd = ::GetWindowLongPtr(hwnd, GWLP_USERDATA);

    WindowMap::iterator it = s_WindowMap->find(wnd);
    if (it != s_WindowMap->end())
    {
    // First see if we have a user message handler for this message
        UserMessageHandler userhandler;
        WindowMessageData msgdata;
        bool hasHandler = false;

        switch (message)
        {
        case WM_CLOSE:
            hasHandler = it->second->GetUserMessageHandler(winmsgs::closemsg, userhandler);
            msgdata.msg = winmsgs::closemsg;
            break;
        case WM_DESTROY:
            hasHandler = it->second->GetUserMessageHandler(winmsgs::destorymsg, userhandler);
            msgdata.msg = winmsgs::destorymsg;
            break;
        case WM_SIZE:
            hasHandler = it->second->GetUserMessageHandler(winmsgs::sizemsg, userhandler);
            msgdata.msg = winmsgs::sizemsg;
            msgdata.param1 = LOWORD(lparam);    // width
            msgdata.param2 = HIWORD(lparam);    // height
            break;
        case WM_ACTIVATE:
            hasHandler = it->second->GetUserMessageHandler(winmsgs::activemsg, userhandler);
            msgdata.msg = winmsgs::activemsg;
            msgdata.param1 = !HIWORD(wparam) ? true : false;
            break;
        case WM_MOVE:
            hasHandler = it->second->GetUserMessageHandler(winmsgs::movemsg, userhandler);
            msgdata.msg = winmsgs::movemsg;
            msgdata.param1 = LOWORD(lparam);
            msgdata.param2 = HIWORD(lparam);
            break;
        default:
        break;
    }

    if (hasHandler)
    {
        if (userhandler(wnd, msgdata))
        {
            return TRUE;
        }
    }

    MessageHandler handler;
    hasHandler = it->second->GetMessageHandler(message, handler);
    if (hasHandler)
    {
        return handler(*(it->second), wparam, lparam);
    }
    else if (message == WM_NCCREATE)
    {
        // attempt to store internal Window handle
        wnd = (WinHnd)((LPCREATESTRUCT)lparam)->lpCreateParams;
        ::SetWindowLongPtr(hwnd, GWLP_USERDATA, wnd);
        return TRUE;
    }
    return DefWindowProc(hwnd, message, wparam, lparam);
}

The code is pretty simple;

  • See if we know how to handle a window we've got a message for (previous setup)
  • If so then go and look for a user handler and translate message data across
  • If we have a handler then execute it
  • If we didn't have user handler then try a system one

The final 'else if' section deals with newly created windows and setting up the map.

So this work, and works well, the pattern is pretty common in C++ code from back in the early-2000s but it is a bit... repeaty.

The problem comes from C++ support and general 'good practise' back in the day; but life moves on so lets make some changes.

The Message Handler Look up

The first problem is the query setup, for which the function which performs the "'do you have a handler?" look up was like this;

bool Window::GetMessageHandler(oswinmsg message, MessageHandler &handler)
{
    MessageIterator it = messagemap.find(message);
    bool found = it != messagemap.end();
    if(found)
    {
        handler = it->second;
    }
    return found;
}

As code goes this isn't hard;

  • We check to see if we have a message handler
  • If we do then we store it in the supplied reference
  • Then we return if we found it or not

Not bad, but it is taking us 5 lines of code (7 if you include the braces) and if you think about it we should be able to test for the existence of the handler by querying the handler object itself rather than storing, in the calling function, what is going on. Along with that the handler gets default constructed on the calling side, which might be a waste too.

So what can C++17 do to help us?
Enter std::optional<T>.

std::optional<T> lets us return an object which is either null or contains an instance of the object of the given type. Later we can look to see if it is valid (via operator bool()) before tying to use it - doesn't that sound somewhat like what was described just now?

So, with a quick refactor the message handler lookup function becomes;

std::optional<MessageHandler>  Window::GetMessageHandler(oswinmsg message)
{
    MessageIterator it = messagemap.find(message);
    return it != messagemap.end() ? it->second : std::optional<MessageHandler>{};
}

Isn't that much better?

Instead of having to pass in a thing and then return effectively two things (via the ref and the bool return) we now return one thing which either contains the handler object or a null constructed object.
(I believe if I had written this as an 'if...else' statement that the return could simply have been {} for the 'else' path but the ternary operator messes that up somewhat, at least in the VS17 RC compiler anyway.)

So, with that transform in place our handling code can now change a bit too; the simple transform at this point would be to replace that bool with a direct assignment to the handler object;

UserMessageHandler userhandler;
WindowMessageData msgdata;
switch(message)
{
case WM_CLOSE:
    userhandler = it->second->GetUserMessageHandler(winmsgs::closemsg);
    msgdata.msg = winmsgs::closemsg;
    break;
// ... blah blah ..

But we still have a default constructed object kicking about, not to mention the second data structure for the message data (ok, so it is basically 3 ints, but still...) - so can we change this?

The answer is yes, changes can be made with the introduction of a lambda and a std::pair.

Enter The Lamdba And The Pair

The std::pair is the easy one to explain. When you look at the message handling code what you get is an implied coupling between the message handler and the data that goes with it; a transformed version of the original message handler data.

So, instead of having the two separate we can couple them properly;

// so this...
UserMessageHandler userhandler;
WindowMessageData msgdata;

// becomes this...
using UserMessageHandlerData = std::pair<UserMessageHandler, WindowMessageData>;

OK, so how does that help us?

Well, on its on it doesn't really however this is where the lambda enters the equation; one of the things you can do with a lambda is declare it and execute at the same type, effectively becoming an anonymous initialisation function at local scope. It is something which, I admit, didn't occur to me until I watched Jason Turner's talk Practical Performance Practices from CppCon2016.

So, with that in mind how do we make the change?
Well, the (near) final code looks like this;

auto userMessageData = [window = it->second, message, wparam, lparam]()
    {
        WindowMessageData msgdata;
    switch (message)
    {
    case WM_CLOSE:
        msgdata.msg = winmsg::closemsg;
        return std::make_pair(window->GetUserMessageHandler(winmsg::closemsg), msgdata );
        break;
    case WM_DESTROY:
        msgdata.msg = winmsg::destorymsg;
        return std::make_pair(window->GetUserMessageHandler(winmsg::destorymsg), msgdata);
        break;
    case WM_SIZE:
        msgdata.msg = winmsg::sizemsg;
        msgdata.param1 = LOWORD(lparam);    // width
        msgdata.param2 = HIWORD(lparam);    // height
        return std::make_pair(window->GetUserMessageHandler(winmsg::sizemsg), msgdata);
        break;
        // a couple of cases missing...
    default:
        break;
    }
    return std::make_pair(std::optional<UserMessageHandler>{}, msgdata);
    }();

if (userMessageData.first)
{
    if (userMessageData.first.value()(wnd, userMessageData.second))
    {
        return TRUE;
    }
}

So a bit of a change, the overall function this is in is now also a bit shorter.

Basically we define a lambda which return a std::pair as defined before, using std::make_pair to construct our pair to return - if we don't understand the message then we simply construct a pair with two null constructed types and return that instead.

Note the end of the lambda where, after the closing brace you'll find a pair of parentheses which invokes the lambda there and then, assigning the values to userMessageData.

After that we simply check the first item in the pair and dispatch if needs be.
So we are done right?

Well, as noted this is nearly the final solution it suffers from a couple of problems;

  1. Lots and lots of repeating - we have make pair all over the place and we have to specify the types in the default return statement
  2. We are still default constructing that WindowMessageData type and assign values after trivial transforms.
  3. That ugly call syntax... ugh...

So lets fix that!

You have a definitive type

The first has a pretty easy fix; tell the lambda what it will return so the compiler can just sort that out for you;

auto userMessageData = [window = it->second, message, wparam, lparam]() -> std::pair<std::optional<UserMessageHandler>, WindowMessageData>
{
    switch (message)
    {
    case WM_CLOSE:
        return{ window->GetUserMessageHandler(winmsg::closemsg), { winmsg::closemsg, 0, 0 } };
        break;
    case WM_DESTROY:
        return{ window->GetUserMessageHandler(winmsg::destroymsg), { winmsg::destroymsg, 0, 0 } };
        break;
    case WM_SIZE: 
        return{ window->GetUserMessageHandler(winmsg::sizemsg), { winmsg::sizemsg, LOWORD(lparam), HIWORD(lparam) } };
        break;
    case WM_ACTIVATE:
        return{ window->GetUserMessageHandler(winmsg::activemsg), { winmsg::activemsg, !HIWORD(wparam) ? true : false } };
        break;
    case WM_MOVE:
        return{ window->GetUserMessageHandler(winmsg::movemsg), { winmsg::movemsg, LOWORD(lparam), HIWORD(lparam) } };
        break;
    default:
        break;
    }
    return{ {}, {} };
}();

How much shorter is that?

So, as noted the first change happens at the top; we now tell the lambda what it will be returning - the compiler can now use that information to reason about the rest of the code.

Now, because we know the type, and we are using C++17, we can kiss goodbye to std::make_pair; instead we use the brace construction syntax to directly create the pair, and the data for the second object, at the return point - because the compiler knows what to return it knows what to construct and return and that goes directly in to our userMessageData variable, which has the correct type setup via auto.

One of the fun side effects of this is that last line of the lambda; return { {}, {} }
Once again, because the compiler knows the type we can just tell it "construct me a pair of two default constructed objects - you know the types, don't bother me with the details".

And just like that all our duplication goes away and we get a nice compact message handler.
Points 1 and 2 handled.

So what about point 3?

Hiding The Call Away

In this case we can take advantage of Variadic Templates, std::invoke and parameter packs to create an invoking function to wrap things away;

template<typename T, typename... Args>
bool invokeOptional(T callable, Args&&... args)
{
    return std::invoke(callable.value(), args...);
}

This simple wrapper just takes the optional type (it could probably do with some form of protection to make sure it is an optional which can be invoked), extracts the value and passes it down to std::invoke to do the calling.

The variadic templates and parameter pack allows us to pass any combination of parameters down and, as long as the type held by optional can be called with it, invoke the function as we need - this means one function for both the user and system call backs;

if (userMessageData.first)
{
    if (invokeOptional(userMessageData.first, wnd, userMessageData.second))
    {
        return TRUE;
    }
}

auto handler = it->second->GetMessageHandler(message);
if (handler)
{
    return invokeOptional(handler, (*(it->second)), wparam, lparam);
}

And there we have it, much refactoring later something more C++17 than C++03.

So here we have it, the message router code in its final current form.

namespace Bonsai::Windowing  // an underrated new feature...
{
    template<typename T, typename... Args>
    bool invokeOptional(T callable, Args&&... args)
    {
        static_assert(std::is_convertible<T, std::optional<T::value_type> >::value);
        return std::invoke(callable.value(), args...);
    }

    WindowMap *WindowMessageRouter::s_WindowMap;

    WindowMessageRouter::WindowMessageRouter(WindowMap &windowmap)
    {
        s_WindowMap = &windowmap;
    }
    WindowMessageRouter::~WindowMessageRouter()
    {
    }

    bool WindowMessageRouter::Dispatch(void)
    {
        static MSG msg;
        int gmsg = 0;

        if (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }

        if (msg.message == WM_QUIT)
            return false;

        return true;
    }

    LRESULT CALLBACK WindowMessageRouter::MsgRouter(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
    {
        // attempt to retrieve internal Window handle
        WinHnd wnd = ::GetWindowLongPtr(hwnd, GWLP_USERDATA);

        WindowMap::iterator it = s_WindowMap->find(wnd);
        if (it != s_WindowMap->end())
        {
            // First see if we have a user message handler for this message    
            auto userMessageData = [window = it->second, message, wparam, lparam]() -> std::pair<std::optional<UserMessageHandler>, WindowMessageData>
            {
                switch (message)
                {
                case WM_CLOSE:
                    return{ window->GetUserMessageHandler(winmsg::closemsg), { winmsg::closemsg, 0, 0 } };
                    break;
                case WM_DESTROY:
                    return{ window->GetUserMessageHandler(winmsg::destroymsg), { winmsg::destroymsg, 0, 0 } };
                    break;
                case WM_SIZE: 
                    return{ window->GetUserMessageHandler(winmsg::sizemsg), { winmsg::sizemsg, LOWORD(lparam), HIWORD(lparam) } };
                    break;
                case WM_ACTIVATE:
                    return{ window->GetUserMessageHandler(winmsg::activemsg), { winmsg::activemsg, !HIWORD(wparam) ? true : false } };
                    break;
                case WM_MOVE:
                    return{ window->GetUserMessageHandler(winmsg::movemsg), { winmsg::movemsg, LOWORD(lparam), HIWORD(lparam) } };
                    break;
                default:
                    break;
                }
                return{ {}, {} };
            }();

            if (userMessageData.first)
            {
                if (invokeOptional(userMessageData.first, wnd, userMessageData.second))
                {
                    return TRUE;
                }
            }

            auto handler = it->second->GetMessageHandler(message);
            if (handler)
            {
                return invokeOptional(handler, (*(it->second)), wparam, lparam);
            }
        }
        else if (message == WM_NCCREATE)
        {
            // attempt to store internal Window handle
            wnd = (WinHnd)((LPCREATESTRUCT)lparam)->lpCreateParams;
            ::SetWindowLongPtr(hwnd, GWLP_USERDATA, wnd);
            return TRUE;
        }
        return DefWindowProc(hwnd, message, wparam, lparam);
    }
}