What’s the point of std::monostate? You can’t do anything with it!

Raymond Chen

C++17 introduced std::monostate, and I used it as a placeholder to represent the results of a coroutine that produces nothing. In the comments, Neil Rashbrook asked what you are expected to do with a std::monostate, seeing as has no members and only trivial member functions.

The answer is “nothing”.

The purpose of std::monostate is to be a dummy type that does nothing. All instances are considered equal to each other. It is basically this:

struct monostate {};
// plus relational operators and a hash specialization

You can see it in libcxx (LLVM/clang), libstdc++ (gcc), and stl (msvc).

But what’s the point of a class that does nothing, and which you can do nothing with?

You don’t use monostate because you want to do something. You use monostate when you don’t want to do anything.

Its original purpose was to be used as the initial type in a std::variant to allow it to be default-constructed in an empty state.

struct Widget
{
    // no default constructor
    Widget(std::string const& id);

    ⟦ other members ⟧
};

struct PortListener
{
    // default constructor has unwanted side effects
    PortListener(int port = 80);

    ⟦ other members ⟧
};

std::variant<Widget, PortListener> thingie; // can't do this

The std::variant‘s default constructor default-constructs its first alternative. Since Widget doesn’t have a default constructor, you can’t put it first. And Port­Listener‘s default constructor has unwanted side effects, so we don’t want to put it first.

Enter std::monostate. You can put that guy first.

std::variant<std::monostate, Widget, PortListener> thingie;

The thingie default-constructs into a monostate. What can you do with a monostate? Nothing! Its job is just to be a dummy type that you can use when you are forced to provide a default-constructible type but don’t want to.

In the case of a std::variant, you can think of inserting std::monostate as a way to add an “empty” state to a variant,¹ saving you the trouble of having to create a std::optional<std::variant<...>>. You can treat the std::monostate as the “empty” state.

In our usage of std::monostate, we used it as just a dummy type that stands in for void

¹ More precisely, a way to add another “empty” state to a variant. There is already an “empty” state for a std::variant, known as valueless_by_exception. This state is wacky, though, so you want to avoid it as much as possible. Another topic for another day.

² Bonus reading: Regular Void, which has stalled.

5 comments

Leave a comment


Newest
Newest
Popular
Oldest
  • Ivan Kljajic 1 week ago 0

    They should have a 1/0 version of it that creates but excepts if you touch it.

  • LB 1 week ago 1

    Thanks for linking the Regular Void document, I was curious what the latest update on it was. Here’s hoping they can find some way to bring it to reality in C++.

  • Mark Magagna 1 week ago 0

    Sounds a lot like the Null Object pattern, where instead of using “null” for something that isn’t there, you construct an object that gives reasonable responses like “I don’t know”. And can do logging etc.

    Which is what null was originally supposed to represent.

    • Kevin Norris 6 days ago 1

      The null object is just an option type, but without proper type checking. There are times when it’s good enough (it is almost always better than a raw null or nil reference, in languages that allow them), and there are times when “I don’t know” or “do nothing” is not a suitable behavior (technically: there are times when a null object is not Liskov-substitutable for a non-null object, usually because the non-null object provides one or more methods that require non-null behavior to fulfill their postconditions – if a method returns bool, you can’t return anything resembling “I don’t know,” because an if statement doesn’t know what to do with “I don’t know”).

      std::monostate is different. It is a null type, not (just) a null object. That is, you cannot pass std::monostate wherever some random other object is required – it has to appear in the type signature. Which, again, sounds useless, but as Raymond has already explained, it can be used to make a “hole” in a generic data type. Rust does this all the time with wrappers like Result and Option. You can return a Result of Ok(()) on the happy path (that’s an Ok containing a () or “unit” object, which is equivalent to std::monostate), and Err(some error) on the sad path.

  • Neil Rashbrook 1 week ago 1

    I keep hoping reading this blog will make me smarter but it doesn’t seem to be working…

Feedback usabilla icon