Skip to content
Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

C++ Reflection: Another Monad

elbeno, 4 March, 20264 March, 2026

“Discovery consists of seeing what everybody has seen, and thinking what nobody has thought.”

— Albert Szent-Györgi, 1937 Nobel Laureate for Medicine

I’m sure others have thought this, but they’re certainly not saying much about it. Barry’s recent blog post “Behold the power of meta::substitute” got so close to saying the words… in fact he even said, “the substitute/extract/invoke dance is remarkably powerful.”

So I’m going to say it plainly: yes! Because substitute is basically fmap, and extract can implement join. Among other things, reflection gives us the monad for type function composition.

To be a monad, we need three things: pure (/ return), fmap and bind or join.

pure :: a -> m a
This is just the reflection operator ^^. It takes a type from “type-land” into “reflection-land”. As a type function:

template <typename T>
constexpr auto pure = ^^T;

fmap :: (a -> b) -> f a -> f b
This is basically substitute:

template <template <typename...> typename F>
consteval auto fmap(std::same_as<std::meta::info> auto... as) {
  return substitute(^^F, {as...});
}

In fact, substitute is a little more than unary fmap, it’s n-ary fmap, which is equivalent to <*> (“ap”). So substitute gives us not only the functor, but the applicative functor.

join :: m (m a) -> m a
bind :: (a -> m b) -> m a -> m b

We have join pretty directly:

consteval auto join(std::meta::info a) {
  return extract<std::meta::info>(a);
}

And given P2841 — which is also in C++26 — we can write bind:

template <template <typename> auto F>
consteval auto bind(std::meta::info a) {
  return join(substitute(^^F, {a}));
}

All good. But hold on a minute — we also need to satisfy a few laws. To do that we need a useful definition of equality, which I’m going to take as:

consteval auto equal(std::meta::info a, std::meta::info b) {
  return dealias(a) == dealias(b);
}

For some reason, reflection deviates from the existing language behaviour with respect to aliases. Perhaps the authors of reflection were concerned with not losing information, or perhaps there is some warty technical reason buried in C++. But at any rate, the language surface up until now does not give us alias differences, so I’m going with alias equality.

Back to the laws — left identity:

// return a >>= h <=> h a

template <template <typename> auto F, typename T>
consteval auto left_id() {
  return equal(bind<F>(pure<T>), F<T>);
}

And right identity:

// m >>= return <=> m

template <std::meta::info M>
consteval auto right_id() {
  return equal(bind<pure>(M), M);
}

The fmap law, for which proof we need an “old-style” composition helper:

// xs >>= return . f <=> fmap f xs

template <template <typename...> typename F>
struct pure_dot_f {
  template <typename... Ts>
  constexpr static auto fn = pure<F<Ts...>>;
};

template <template <typename...> typename F, typename... Ts>
consteval auto fmap_law() {
  return equal(bind<pure_dot_f<F>::template fn>(^^Ts...), fmap<F>(^^Ts...));
}

Of course the composition helper is just to show that this is correct according to “the old ways”, which are clunky. Where we’re going, we won’t need it any more.

And finally the associativity law — once again, with an old-style helper:

// (m >>= g) >>= h <=> m >>= (\x -> g x >>= h)

template <template <typename> auto G, template <typename> auto H>
struct assoc_rhs_part {
  template <typename X>
  constexpr static auto fn = bind<H>(G<X>);
};

template <template <typename> auto G, template <typename> auto H>
consteval auto assoc_law(std::meta::info m) {
  auto lhs = bind<H>(bind<G>(m));
  auto rhs = bind<assoc_rhs_part<G, H>::template fn>(m);
  return equal(lhs, rhs);
}

And Robert is your mother’s brother. https://compiler-explorer.com/z/o4fG3P5K3

So this is (one reason) why reflection is powerful: among other things, it implements the type function composition monad. With ^^, substitute and extract<std::meta::info> we have everything we need. And once you grok this, you’re off to the races. Makes sense, right? One intuition for monads is that they lift values and computations into a new world — in this case, the reflected world.

C++26 gives us type function composition machinery in the language, and TMP libraries everywhere will be able to take advantage.

C++

Post navigation

Previous post
Next post

Related Posts

A persistent myth about STL’s remove (and friends)

8 March, 201530 June, 2015

There seems to be a persistent myth about STL’s remove, remove_if, etc. Ask even a relatively experienced C++ programmer to explain this code. vector v = { 1,2,3,4,5 }; v.erase(remove_if(v.begin(), v.end(), [] (int i) { return (i & 1) == 0; }), v.end()); They’ll recognize the erase-remove idiom and correctly…

Read More

An algorithmic sketch: inplace_merge

13 March, 201618 March, 2016

One of the things I like to do in my spare time is study the STL algorithms. It is easy to take them for granted and easy, perhaps, to imagine that they are mostly trivial. And some are: I would think that any decent interview candidate ought to be able…

Read More

Another myth, about C++ lambdas

16 March, 201530 June, 2015

Myth: Lambda expressions can cause heap allocations. I see this myth coming up a lot. People think that lambdas can cause a heap allocation – on Reddit, on StackOverflow, on Channel9 comments, in personal conversations. I don’t blame people for thinking this – C++ is complex. But it always seems…

Read More
©2026 Why is a raven like a writing desk? | WordPress Theme by SuperbThemes