Proposal: beman.monadics - Free-function monadic operations for box-like types

Hi everyone, long time no see!

I would like to propose a new library for the Beman Project: beman.monadics.

C++23 introduced monadic operations for std::expected, and C++26 extends them to std::optional. But raw pointers, smart pointers, and many third-party types currently have no unified monadic interface. beman.monadics aims to provide a free-function, extensible monadic interface for any “box-like” type.

P.S. I first proposed this idea during C++Now 2025 Library in a Week, but unfortunately didn’t have time to follow up back then.

Design

Operations are free functions and support pipeline syntax:

namespace bms = beman::monadics;

std::optional{10}
| bms::and_then([](auto&& v){ return std::optional{v*2}; })
| bms::transform([](auto v){ return static_cast<int>(v); })
| bms::or_else([](){ return std::optional{0}; });

Core operations:

  • and_then

  • transform

  • or_else

  • transform_error

All operations preserve value category and const correctness.

Customization

Types opt in via box_traits (similar to std::formatter):

template <typename Box>
struct beman::monadics::box_traits;

The full interface looks like this:

template <typename Box>
struct beman::monadics::box_traits<Box> {
    using value_type = ...;
    using error_type = ...;

    template <typename T>
    using rebind = ...;

    template <typename E>
    using rebind_error = ...;

    static constexpr bool has_value(const Box&) noexcept { ... }
    static constexpr value_type value(auto&&box) noexcept { ... }
    static constexpr decltype(auto) error(auto&&box) noexcept { ... }
    static constexpr decltype(auto) make(auto&& v) noexcept { ... }
    static constexpr decltype(auto) make_error(auto&& e) noexcept { ... }
};

Some members are optional. For example, std::optional only requires the error() member; the rest (has_value, value, value_type, rebind, etc.) can be deduced automatically via template metaprogramming. Similarly, make and make_error can be generated if the type is constructible from a value and/or error.

Examples:

Status & Background

  • Experimental prototype with tests and CI passing

  • Willing to maintain and evolve under Beman

Repository: https://github.com/msvetkin/monadics

Questions

  • Is this still something the project is interested in?

  • If yes, what is the process?

    • Design review

    • Submission steps

  • Should the initial scope be even smaller?

  • Who can help with writing a paper?

Hi @msvetkin - welcome back!

Sorry for the delayed repsonse – everything is busy. I’ll note that we also have a discord these days which I would suggest giving a ‘heads-up’ there to make sure people see it.

I think it’s an interesting approach. Of course with ranges using operator| I wonder if we’ll run into odd overloading issues – noting that beman.optional is now range enabled. I confess I’m not a compiler, so I’d have to try it.

Core operations: …

How does value_or fit into this? There’s also this proposed addition: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3413r0.pdf .

what is the process

So coming under the umbrella of the project is pretty straight-forward. You can transfer the repo under the project trivially. With respect to design feedback, in some respects this is a novel case for the project. This is a case of needing really early feasibility review. I’ll bring it up at the weekly sync meeting, but I’d propose we have an announced review/discussion to give you feedback. No sense in writing papers if other folks think this will be dead-on-arrival at the committee.

smaller scope

I’m not sure how?

paper

Let’s work on the above first and then see if people think it’s worth the effort.

Hello @Jeff-Garland

No worries at all. I wasn’t aware there was a Discord — thanks for the suggestion. I’ll post a heads-up there as well.

I think it’s an interesting approach. Of course with ranges using operator| I wonder if we’ll run into odd overloading issues – noting that beman.optional is now range enabled. I confess I’m not a compiler, so I’d have to try it.

Regarding the pipeline approach: it is implemented similarly to the std::ranges piping mechanism (i.e. constrained customization point objects). The operators are properly constrained via concepts, so they only participate in overload resolution for is_box types. In principle, this should avoid interference with ranges — though I agree it’s something worth validating carefully.

I’ll put together some experiments to verify that interaction explicitly.

I’ll bring it up at the weekly sync meeting, but I’d propose we have an announced review/discussion to give you feedback. No sense in writing papers if other folks think this will be dead-on-arrival at the committee.

If it would be helpful, I’d be happy to join the weekly sync or any scheduled review to answer questions live.