ned Productions Consulting

Technology musings by Niall Douglas
ned Productions Consulting
(an expert advice and services company based in Ireland)

Saturday 13th June 2015 4.02am

Link shared:

As much as I only just said earlier today that my world's simplest C++ monad was going to be obsessive about keeping build times low and therefore avoiding all metaprogramming, there is one niche area where heavy metaprogramming makes a lot of sense: the monadic bind and map operations. Under the "you only pay for what you use" principle, if you are using those monadic programming operations it means you accept substantial build time costs over doing the same operations manually by hand. If you don't use them, there is a slight cost to the parser in parsing, but as it's all template stuff nothing gets instantiated until on demand.

Let's take a quick example: In my monad, monad<int> can have state int, empty, error_code or exception_ptr.

monad<int> foo;

Let's do a bind on that: 

monad<void> ret1(foo.bind([](int){/* only called if foo contains an int, else a monad of this lambda's return type is created with the propagated error/empty state /}));

monad<int> ret2(foo.bind([](int &&v){ return std::move(v); /
same as before,  but we move from foo into ret2 /}));

monad<int> ret3(foo.bind([](error_code e){ /
whoah, this is different! Now only called if foo contains an error code! Always returns same monad as originating monad as any other states get propagated / }));

monad<int> ret4(foo.bind([](){ /
callables taking no parameters only get called if foo is empty / }));

monad<void> ret5(foo.bind([](auto){ /
C++ 14 generic lambdas are assumed to be value consuming / }));

monad<void> ret6(foo.bind([](auto &&){ /
and yes, this supplies an rvalue ref to the value same as always / }));

monad<void> ret7(foo.bind([](monad<T>){ /
it goes without saying this also works */ }));

This inspection of the parameter which the callable takes to determine if it is a rvalue ref and what type it is and therefore effectively implement overloading of bind() and map() based on the call specification of the function supplied is to my best knowledge unique to my monad design.

I can get away with it because all empty, error_code and exception_ptr is always an error, and any other type is always the fixed value type of the monad. Also, for a monad<T> there is always an implicitly available conversion into a monad<T> from T, error_code and exception_ptr plus T may not be error_code nor exception_ptr, so most of the time you rarely create a monad<T> explicitly but instead let the compiler implicitly construct one for you.

In other words, the fixed function nature of this very simple monad gives an unparalleled lack of need to type out code as most other monad libraries require. 

Curious how one inspects the first parameter of some arbitrary possibly templated callable type in C++ 14 which works on VS2015, GCC and clang? I had to go ask Stack Overflow myself in fact as it is not obvious, and the link to wandbox below is a live demo of the callable type deduction code.

All credit for that introspection code goes to T.C. at the question I just tidied it up and made it generic to any callable type.