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 Crossword for CppCon 2024 – Solutions

19 September, 202414 September, 2024

As a followup from last week, here’s an explanation of the CppCon crossword. I’m going to explain the construction process a bit, as well, so the solutions will be given in order of clue-writing, with some commentary on my thought process. Maybe a few people may find this interesting, I…

Read More

“The Lambda Trick”

18 May, 201530 June, 2015

I just got back from C++Now, an excellent conference where C++ template metaprogramming experts abound. A phrase I overheard often was “the lambda trick”. It’s a trick for speeding up compiles when templates get deep. Every C++ programmer knows that deep templates can slow down compilation. Most assume that this…

Read More

VS2010 woes: tuples as map keys

20 February, 201530 June, 2015

Another day, another compiler/library bug. If you’re unfortunate enough to still be using Visual Studio 2010, don’t use tuples as map keys. #include #include #include using namespace std; typedef tuple FooT; typedef map MapT; int main(int, char*[]) { MapT m; // put a value in the map { FooT f(nullptr,…

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