[HN Gopher] What's the point of std:monostate? You can't do anyt...
       ___________________________________________________________________
        
       What's the point of std:monostate? You can't do anything with it
        
       Author : luu
       Score  : 84 points
       Date   : 2024-07-19 02:15 UTC (3 days ago)
        
 (HTM) web link (devblogs.microsoft.com)
 (TXT) w3m dump (devblogs.microsoft.com)
        
       | pavlov wrote:
       | There's a secret competition between C++ and JavaScript to come
       | up with more variants of nothing.
        
         | ThrowawayTestr wrote:
         | Mathematicians got nothing on lazy programmers
        
         | immibis wrote:
         | VBA has 4 kinds. Null, Nothing, Missing, and Empty.
        
         | shadowgovt wrote:
         | If you like that, you'll love how many ways you can test for
         | equality in LISP.
        
           | MathMonkeyMan wrote:
           | That always chafed me about Scheme. I see the utility of
           | `eq?` and `eqv?`, but I'd prefer that there were only
           | `equal?` and functions were defined to get an object's "id"
           | or "numeric equivalency class," or whatever, instead of
           | having different flavors of data structures that differ only
           | for certain values.
        
       | loa_in_ wrote:
       | It's probably more of use to compiler/runtime developers than end
       | users if the language.
        
         | jcelerier wrote:
         | It's useful whenever you want a variant with a default state
         | which is different from any value you'd actually put it it, to
         | differentiate between e.g.                   std::variant<int,
         | whatever> v;         // v is zero
         | 
         | and                   std::variant<int, whatever> v = 0;
         | 
         | And don't want to pay the space and time overhead of wrapping
         | in an std::optional which will use a whole other byte at least
         | for not good reason
        
           | glitchc wrote:
           | > And don't want to pay the space and time overhead of
           | wrapping in an std::optional which will use a whole other
           | byte at least for not good reason
           | 
           | This. monostate's behavior should be captured in the
           | std::optional spec. There's no need to create a new type.
        
             | masklinn wrote:
             | That is opposite of sense. monostate is a much more general
             | concept, if you want to argue about the need for a new
             | type, the answer is to remove optional because it's a
             | `variant<T, monostate>`. It doesn't work the other way
             | around.
        
               | glitchc wrote:
               | That's putting the cart before the horse. Languages are
               | designed for developers to use, not compilers to
               | optimize. The intention and use of std::optional is
               | clear.
               | 
               | Edit: My point, if not clear, is that the compiler should
               | add the extra bit of code for when an optional is empty
               | automatically, rather than requiring that an optional be
               | defined (it's optional!) unless it is explicitly typed as
               | monostate.
        
               | masklinn wrote:
               | That makes even less sense, what "extra bit of code" are
               | you talking about? monostate is not designed to be used
               | with optional: although there's the odd case where that's
               | useful an optional<monostate> is bijective to a boolean,
               | and because C++ does not have zero-sized types it's less
               | efficient (as it takes two bytes).
        
           | ryukoposting wrote:
           | Parton my naivete, but if you wanted a value that's either a
           | thing, or not-a-thing, why wouldn't you express that with a
           | std::optional? What advantage does std::monostate have versus
           | option types?
           | 
           | Brevity, I guess? I suppose the most brief way to express
           | absence or presence of value in C++ is a pointer, but I could
           | be tried at the Hague for that take.
           | 
           | None of this is to be facetious, I'm really trying to learn
           | here. I'm not a C++ guy by trade. C and Rust are my day-to-
           | day languages.
        
             | OskarS wrote:
             | You're not wrong, if you just want "a T or nothing",
             | optional is the way to go. But what if you want "a T, a U,
             | a V, or nothing"? Then you do
             | std::variant<std::monostate, T, U, V>
             | 
             | Or                   std::optional<std::variant<T, U, V>>
             | 
             | But then the "none" state is a bit more "special", it's not
             | just one of the options. The sizeof of the type will also
             | probably be a bit bigger, because it has to contain both
             | the bool for the optional, as well as the tag for the
             | variant.
             | 
             | The other obvious use is what the article states, that it
             | allows the variant to be default constructed even if none
             | of its members are. Though you can do that with optional as
             | well. It's mostly a matter of style. I mostly avoid
             | std::monostate because the name is so confusing, I really
             | agree with the other users that something like std::unit or
             | std::none would be better.
        
               | ryukoposting wrote:
               | > it allows the variant to be default constructed even if
               | none of its members are
               | 
               | Ah, okay! So it's serving the same purpose as the "None"
               | in this Rust snippet:                   enum Foo {
               | None, // <-- impl core::Default and return this
               | T(T),             U(U),             V(V),         }
               | 
               | That makes sense, though it really shows how the legacy
               | of C++'s type system limits stdlib design in the present
               | day. It'd be awfully nice to be able to just write
               | std::variant<void, T, U, V>.
        
               | jcelerier wrote:
               | I mean, it's very trivial to make your own basic wrapper
               | with "void" in the API if that's what you want:
               | https://gcc.godbolt.org/z/v6EPTaaPY
        
               | ryukoposting wrote:
               | I suppose that code is "trivial" in the same sense that
               | anything else involving C++11 is "trivial."
        
           | masklinn wrote:
           | That... doesn't make any sense?
           | 
           | The size of an optional<T> is `sizeof T + 1` because it needs
           | a boolean for the set/unset flag.
           | 
           | The size of a variant is... the same, at least, because it
           | needs to store a discriminator integer (apparently both gcc
           | and clang do optimise to a byte when there are less than 256
           | variants).
           | 
           | Or do you mean adding a monostate to _an existing variant_
           | rather than wrap that variant into an `optional`? In which
           | case you should fix your example (and provide a version
           | wrapped in an optional) because they're very confusing.
        
             | jcelerier wrote:
             | I don't understand what's confusing with my example aha.
             | 
             | Current situation: you have
             | std::variant<int, whatever> x;
             | 
             | you now want to discriminate on whenever x has been
             | initialized explicitly or not, the two cases I posted:
             | // case 1         std::variant<int, whatever> x;
             | // case 2         std::variant<int, whatever> x = 0;
             | 
             | you have two options:
             | 
             | option A: does not needlessly increase sizeof, does not add
             | indirection penalty upon access:
             | std::variant<std::monostate, int, whatever> x;
             | 
             | option B: needlessly increases sizeof, adds indirection
             | penalty upon access (mostly relevant when compiling in
             | debug mode without inlining if you still want to have a
             | semblance of performance):
             | std::optional<std::variant<int, whatever>> x;
        
         | jandrewrogers wrote:
         | It has other uses for end-user developers. For example, when
         | you need a class member to be conditionally elided based on
         | template parameters. You can swap the normal type with non-zero
         | size with std::monostate such that it has zero size.
        
           | tpolzer wrote:
           | A std::monostate member will still have non zero size,
           | because it needs a unique address.
           | 
           | See https://en.cppreference.com/w/cpp/language/ebo
        
             | jandrewrogers wrote:
             | ...except if you use the standard [[no_unique_address]]
             | attribute.
        
       | ramon156 wrote:
       | So the upside is that you can put it at the beginning of a
       | function? Wild, bro
        
         | Joker_vD wrote:
         | You can also use it in any template without any trickery unlike
         | void, which I am willing to bet was the actual rationale.
        
           | meibo wrote:
           | The article describes that as the main advantage, showing
           | std::variant as an example.
        
         | Smaug123 wrote:
         | Having access to the unit type _is_ useful; I use it maybe once
         | every couple of months in F# even if we restrict solely to the
         | use-case  "instantiate a generic with the unit type to indicate
         | that no data is held here". (Of course, since F# also doesn't
         | have a `void` type - a truly non-constructible data type is
         | indeed _very_ rarely useful! - F# uses `unit` in many places
         | where C++ and C# use `void`.)
        
       | lloydatkinson wrote:
       | So it appears this follows the terrible C++ "naming things with
       | the wrong name on purpose" trend, with the biggest example being
       | naming a variable length array (a list, collection, etc)
       | `std:vector` even though Vector was already a well known word
       | with a very different meaning.
       | 
       | The word/type they wanted for this was the Unit type:
       | https://en.wikipedia.org/wiki/Unit_type and in that list it even
       | states the `std:monotype` is a Unit.
        
         | codeflo wrote:
         | Functor is one of the worst offenders IMO. In C++ lingo, it
         | simply means "callable object".
        
           | halayli wrote:
           | In C++ lingo they are called function objects or callables,
           | not functor. You might stumble on code here and there that
           | suffix a class name with Functor but it's not that common.
        
             | codeflo wrote:
             | Many blog posts and Stackoverflow questions about C++ cal
             | them functors. Boost of all things uses the term in its
             | definitions: https://www.boost.org/doc/libs/1_60_0/doc/html
             | /function/refe...
             | 
             | It's common enough that articles have been written against
             | this usage: https://web.archive.org/web/20170224220253/http
             | ://jackieokay...
             | 
             | So this usage of functor may never have been "official",
             | but it's widespread in the community.
        
               | plorkyeran wrote:
               | I would say it _was_ widespread, but it 's now fallen out
               | of favor. Alexandrescu got the name wrong 25 years ago+
               | and that got embedded into early boost, but by the time
               | that blog post was written I feel like I'd mostly stopped
               | seeing it being used by C++ experts.
               | 
               | + I don't know if the original mistake was his, but he
               | certainly did a lot to spread it.
        
               | Maxatar wrote:
               | cppreference.com uses the term functor in numerous places
               | for recent C++ features including things in C++26 such as
               | reflection:
               | 
               | https://en.cppreference.com/w/cpp/experimental/reflect
               | 
               | https://en.cppreference.com/w/cpp/utility/variant/visit2
               | 
               | https://en.cppreference.com/w/cpp/experimental/parallelis
               | m
               | 
               | Visual Studio's official documentation uses the term
               | functor as of 2021:
               | 
               | https://learn.microsoft.com/en-us/cpp/standard-
               | library/funct...
               | 
               | A simple Google search shows no shortage of recent
               | websites and documents including fairly authoritative
               | references using the term functor to describe a type that
               | overloads operator ().
               | 
               | Heck even the author of this article, Raymond Chen, uses
               | the term functor as recently as 2020 to describe such a
               | construct:
               | 
               | https://devblogs.microsoft.com/oldnewthing/20200513-00/?p
               | =10...
               | 
               | Is Raymond Chen not a C++ expert?
        
             | sgbeal wrote:
             | > In C++ lingo they are called function objects or
             | callables, not functor.
             | 
             | The word "functor" has a long and glorious history in C++.
             | Try entering "C++ Andrei Alexandrescu functors" into your
             | internet search engine of choice. For bonus points, try
             | "c++ Scott Meyers functor" as well.
        
             | Maxatar wrote:
             | The creator of C++ uses the term functor in the book "The
             | C++ Programming Language" to describe any object that
             | overloads operator ().
        
           | layer8 wrote:
           | It's not like anyone has a monopoly on the term:
           | https://en.wikipedia.org/wiki/Functor_(disambiguation)
           | 
           | The C++ usage was introduced by Jim Coplien in his 1992 book
           | as a succinct name for an architectural pattern in C++: https
           | ://archive.org/details/advancedcbsprogr00copl/page/166/...
        
         | sham1 wrote:
         | I'd argue that unit would be just as cryptic as monostate, if
         | you don't know what either is.
         | 
         | Like, if we assume someone looking at code with `std::unit`,
         | what might they think this is? If one is not aware of its use
         | in ML or similar, it could just as easily be assumed that it
         | could be something to do with units like meters or kilograms or
         | whatnot. After all, the C++ standard library is vast so it
         | wouldn't necessarily be all that far-fetched.
         | 
         | Then the only question would be to ask why it would be default-
         | constructible. At which point you'd have to read the docs for
         | the type anyway.
        
           | mananaysiempre wrote:
           | It's called "unit" because it is a unit of the operation of
           | multiplication / constructing a tuple (up to a unique
           | isomorphism): for any type 't the tuple types unit * 't and
           | 't * unit are isomorphic to 't. When you don't have that as a
           | fundamental operation in your type system, like ML does, then
           | it could be confusing.
        
             | xdavidliu wrote:
             | sure, but I would be surprised if a significant fraction of
             | programmers knew that. When we hear the word "unit", that's
             | not the first definition that comes to mind.
        
           | omnicognate wrote:
           | There is indeed a units library [0] aiming for
           | standardisation in C++29.
           | 
           | [0] https://github.com/mpusz/mp-units
        
         | Scubabear68 wrote:
         | My guess is the maintainers of C++ keep making it more and more
         | awful in an attempt to force people to more sane languages.
         | 
         | But to their dismay the crazier it gets, the more some people
         | dig in and embrace it even more.
         | 
         | /sarcasm. I think.
        
           | andersa wrote:
           | It's not sarcasm. The maintainers have completely lost the
           | plot.
        
             | IncreasePosts wrote:
             | Even Herb Sutter agrees(I think, in his heart, even if he
             | doesn't come out directly and say it), which is why he is
             | working on cppfront
        
         | atoav wrote:
         | Coming from other languages I noticed this about C++ as well. I
         | can't give examples right now but I recall multiple times being
         | like: "Oh that is just a weird name for
         | $KnownComputingConcept".
        
         | halayli wrote:
         | The term "vector" represents an ordered collection of
         | elements(ex 3-dim vector is [x,y,z]). A list is flat out
         | incorrect because it is used to refer to linked
         | list(std::list), and a collection means a group of objects and
         | that can be anything like a map, set, etc so it's too generic.
         | 
         | Before C++ popularized the term, other programming languages
         | and libraries had already used "vector" to describe similar
         | data structures. For example, Common Lisp has a vector type
         | that represents a one-dimensional array.
        
           | Maxatar wrote:
           | Stepanov introduced the term in C++ and he fully acknowledges
           | that it was a bad name and that he regrets it. If he could
           | redo it, he would have renamed it array or array_list.
           | 
           | Interview with him acknowledging this:
           | 
           | https://www.youtube.com/watch?v=etZgaSjzqlU
        
           | Y_Y wrote:
           | It doesn't help that mathematics and physics have their own
           | overlapping but unequal definitions of "vector".
        
         | yndoendo wrote:
         | Reactive style programming also takes the Unit as the value to
         | act upon when the value and the type does not actually matter
         | and only the action does.
         | 
         | https://stackoverflow.com/questions/54336641/is-there-any-re...
        
         | gpderetta wrote:
         | Consider ML appropriating the term tensor...
        
       | nmeofthestate wrote:
       | Another use of std::monostate is as a special "unset/don't care"
       | value for a template parameter. eg
       | template<typename T = std::monostate>         class C         {
       | ...                  if constexpr (!std::is_same_v<T,
       | std::monostate>             {                 // T-related
       | behaviour here             }         };
        
         | ot wrote:
         | You can use void for that.
        
           | Maxatar wrote:
           | void could be an actual meaningful type for a template rather
           | than a dummy type.
        
         | klyrs wrote:
         | I greatly prefer tag classes for this. You can define 'em in a
         | single line and a downstream user can't accidentally plug the
         | unset value in to the template.
        
       | amne wrote:
       | so "std::nothing" was taken? or "std::none" ? or anything else
       | that would make it obvious this type is a fancy way to say void?
        
         | rwmj wrote:
         | Or "unit" in ML-derived languages and Haskell.
        
           | fire_lake wrote:
           | I always think of unit as one - it has exactly one possible
           | value.
           | 
           | Void doesn't exist in ML
        
             | ackfoobar wrote:
             | log 1 = 0, so it has exactly zero bits of information.
        
             | millimeterman wrote:
             | Void definitely exists in ML.                 --- Haskell
             | data Void            (* Standard ML *)       datatype void
             | = Void of void
        
         | masklinn wrote:
         | "Nothing" can easily be interpreted as an uninhabited type
         | (regardless of its use in haskell).
         | 
         | > a fancy way to say void?
         | 
         | Less fancy and more workable. Had void been a proper type in
         | the first place it would not have been needed (but also... void
         | had the same issue as nothing, it sounds like an uninhabited
         | type more than a unit type).
         | 
         | Despite that, they could have called it Void, even if the
         | standard library normally uses all lowercase.
        
           | ackfoobar wrote:
           | Capital letter `Void` reminds me of how Java uses the object
           | type `Void` for this purpose, as all reference types allow
           | null.
        
         | proaralyst wrote:
         | Void is different from this type though, as a variable of type
         | void can't be occupied.
         | 
         | In ML and friends monostate is called unit (and gets used a lot
         | because void returns aren't allowed by the languages). Some
         | have empty types too, which can never be occupied. A function
         | returning Empty can't return, for example, though there are
         | other use cases
        
           | cvoss wrote:
           | You are equivocating on the word "void". Your statement that
           | "a variable of type void can't be occupied" is true in
           | functional languages where "void/Void" is often used as the
           | name of a type that isn't inhabited (assuming the language is
           | sound/normalizing/whatever).
           | 
           | But here we are talking about C++, where "void" is a
           | pseudotype that is absolutely inhabited, in some conceptual
           | sense. Any function that is declared to return void and which
           | returns is returning a thing that conceptually inhabits void.
           | In this sense, std::monostate indeed captures the same
           | concept as void, but in a much better way, because it's
           | properly a type, not a pseudotype.
           | 
           | Note: Java does the same thing, effectively, with "Void"
           | which is inhabited by exactly one value: null.
        
             | proaralyst wrote:
             | I think it's not correct to say that void is a monotype in
             | C++, because the compiler won't allow you to assign the
             | result of a function marked void to a variable, and you
             | cannot declare a variable of type void.
             | 
             | I'd accept that it's not the same as the empty type though,
             | given that void* can be occupied and functions marked void
             | can return. Probably someone with more type theory than me
             | can name this properly
        
               | saurik wrote:
               | Which is really annoying, and makes a ton of templated
               | code in C++ have have to bifurcate on void unnecessarily.
               | They already let me do return f(); in a void function if
               | f also returns void... they should let me declare a
               | variable of type void and the language is going to become
               | a lot more pleasant.
        
               | Y_Y wrote:
               | What are you going to put in that variable?
               | void f();         void v = f();         void g(a){return
               | a;}         v = g(v);
               | 
               | Maybe my imagination is failing me, but I can't see how
               | this can do much good without at least polymorphic
               | functions.
        
               | gpderetta wrote:
               | template<range R, regular X, invocable<range_value<R>, X>
               | F>          requires same_as<invoke_result_t<F, R, X>, X>
               | auto fold(R&& range, F f, X accumulator) {
               | for(auto x: range)               accumulator = f(x,
               | accumulator);           return accumulator;        }
               | 
               | I can call that with a function returning a custom unit
               | type:                  enum class void_t { Void };
               | fold(my_range, [](auto&& elem, void_t) { return Void; },
               | Void);
               | 
               | But not with void:                  fold(my_range,
               | [](auto&& elem, void) { return; }, void{});
               | 
               | which is very annoying and requires fold to special case
               | 'void' via metaprogramming.
        
               | remexre wrote:
               | Templates already provide that useful polymorphism;
               | template<typename T>         T callWithState(auto f) {
               | auto old = globalState;             globalState =
               | whatever();             T out = f();
               | globalState = old;             return out;         }
               | 
               | (forgive any syntax errors, my C++ is very rusty...)
        
               | CuriousSkeptic wrote:
               | Not that familiar with c++ but used to have this thought
               | about both Java and C#. Think I've changed my stance on
               | it now though.
               | 
               | If following something like CQS the bifurcation can be
               | thought of allowing "pure" functions and excluding code
               | with a temporal / side-effecting component from higher
               | order code.
               | 
               | Not saying bifurcating on void is the best approach to
               | handle that, but in languages where side effects are a
               | thing something is needed to make sure higher order code
               | and side effecting code mix properly.
        
               | ackfoobar wrote:
               | I'm not that familiar with C, or C++. My impression is
               | that void is a special case that doesn't need to be
               | special, some accidental complexity that came from
               | mapping machine instructions to a higher level language.
        
               | rerdavies wrote:
               | [delayed]
        
             | Koshkin wrote:
             | Incidentally, the classic C did not have 'void'; instead,
             | it was assumed that any function would, by default, return
             | 'int' in the form of some value stored in the accumulator,
             | and so the "value" of 'void' would be effectively
             | represented by random garbage. The 'void' that was
             | introduced explicitly in a later version of C weakened the
             | original meaning of the unknown value by allowing pointers
             | to 'void' and thus not requiring that the value pointed to
             | must be always thought of as meaningless (since you could
             | cast a pointer to void to a pointer to something else).
        
         | kevinventullo wrote:
         | I think there is exactly one equivalence class of instances of
         | std::monostate, whereas there are exactly zero equivalence
         | classes of instances of void.
         | 
         | In category theory terms, I believe void is the _initial_ type
         | (there is exactly one morphism from void to any other type),
         | whereas monostate is the _terminal_ type (there is exactly one
         | morphism from any other type to monotype).
        
         | rerdavies wrote:
         | std::void_t would have been nice.
        
       | revskill wrote:
       | Is it like a singleton without any method ?
        
         | masklinn wrote:
         | Since it doesn't have any data either (hence mono _state_ )
         | that's not a useful distinction. It's a singleton in the same
         | way 1 is a singleton.
        
       | formerly_proven wrote:
       | Seems almost intentionally confusingly written. Why not use void
       | if monostate is like void? Ah, because monostate is actually
       | entirely unlike void. void has zero values, mono state exactly
       | one.
        
         | leni536 wrote:
         | You can have as many monostate objects as you want.
        
         | mananaysiempre wrote:
         | void is kind of strange in C, because it looks like an empty
         | type, but it still behaves as though it has a single instance
         | (a unit type). For example, a function with an empty return
         | type can't return (it'd have to supply a value of it); a void
         | function can. You can't cast things to an empty type (otherwise
         | you'd get a value of it), you can cast things to void. Void a
         | unit type, not an empty type, it's just a bad one.
        
           | formerly_proven wrote:
           | It would be somewhat more cohesive and less weird if we'd
           | argued that the "void" return type means that a return value
           | cannot be constructed which is taken to mean that the
           | function has no return value, and simply returns without
           | providing a value.
        
             | mananaysiempre wrote:
             | Try as I may, I can't make sense of that. I've read
             | something like it in books on C, but I still can't. Maybe
             | I'm infected with set theory too deeply.
             | 
             | In my mind, a computation (a "function") must either return
             | a value or hang/crash. If it appears as though it returns a
             | member of [?], it must hang/crash, because there are no
             | members of [?]. If it returns a member of the single-
             | element set, [1], it can return one, there's just no use
             | inspecting it afterwards (you know what it is already).
             | 
             | (For what it's worth, if you use a prover-adjacent language
             | such as Agda or Idris, this is exactly how things are going
             | to work there.)
        
               | Joker_vD wrote:
               | > a computation (a "function") must either return a value
               | or hang/crash.
               | 
               | It can also simply return control, without returning
               | anything. It's equivalent to invoking a continuation with
               | zero arguments. Do you allow for zero-argument functions,
               | at least?
               | 
               | Of course, if a function can't return no value
               | whatsoever, you suddenly need new syntactical categories
               | to support it: you need to prohibit using such functions
               | in an expressions (only call statements are allowed), you
               | need a way to return from such a function (naked
               | "return", which is prohibited from taking any
               | expressions), and it's also severely strains your
               | generics/templates because you can't treat such functions
               | uniformly etc.
        
               | shiandow wrote:
               | You encounter the same problem with zero argument
               | functions.
               | 
               | A function on a zero type would be unable to return _any_
               | value, since there 's no value you could apply it to. To
               | have a function of zero arguments you should use the unit
               | type (which means the function effectively picks out a
               | single value).
               | 
               | This is also related to how a function with multiple
               | arguments is a function of the product type, and an empty
               | product is 1 not 0.
        
               | Joker_vD wrote:
               | No, you just invoke the function and pass it zero
               | arguments, that's it.
               | 
               | Sure, you can build your whole theoretical framework of
               | computation with only the functions of exactly one
               | argument, and then deal with tuples to fake multi-valued
               | arguments/multiple return values -- but you don't have to
               | do that. You may as well start from the functions with
               | arbitrary (natural) number of arguments/return values,
               | it's not that hard.
        
               | shiandow wrote:
               | Sure, and passing it zero arguments is exactly what it
               | means to evaluate it on the single value of the unit set.
               | 
               | I mean surely we can agree that a pure function of 0
               | arguments picks out exactly 1 value, and that a function
               | that accepts n different values (values not arguments) as
               | input returns at most n different results? Why make an
               | exception for n=0?
               | 
               | Your definition of a function of 0 arguments and that of
               | a function over the unit set are identical. Or at least
               | equivalent.
        
               | Joker_vD wrote:
               | They're equivalent, but only up to whatever computational
               | substrate one is actually using. You can build functions
               | out of small-step operational semantics of, say, a
               | simplistic imperative register machine with a stack. In
               | this case, a function of 0 arguments and a function of 1
               | trivial unit argument are visibly different even though
               | their total effect on the state is the same. After all,
               | we're talking about theory of _computation_ and so it
               | better be able to handle computations as they are
               | actually performed at the low level, too.
               | 
               | It's yet another example of "in theory, the theory and
               | the practice are the same; in practice, they're
               | different": I have written a toy functional language that
               | compiles down to C, and unit-removal (e.g. transforming
               | int*()*int into int*int, lowering "fun f() -> whatever =
               | ..." into a "whatever f(void) {...}" etc.) is a genuine
               | optimization. The same, I imagine, would apply to
               | generating raw assembly: you want to special-case
               | handling of unit so that passing it as an argument would
               | _not_ touch %rdi, and assigning a unit to a value should
               | not write any registers, and  "case
               | unit_producing_function(...) of -> ... end" actually has
               | no data dependency on the unit_producing_function etc.
        
               | shiandow wrote:
               | How would one write a 0 bit value into registers?
        
               | Joker_vD wrote:
               | Yes, that's the problem you face when you're dealing
               | strictly with functions of a single argument. Still,
               | there are two options: first, it's arguably is already
               | written into the register so you don't need to do
               | anything.
               | 
               | Alternatively, you may instead represent () as a full,
               | 64-bit wide machine word and then map every 64-bit value
               | to mean () so, again, you don't actually need to write
               | anything: all registers contain a valid representation of
               | () at all times. This is similar to how we usually
               | represent booleans: 0 is mapped to mean False, and
               | everything else is mapped to mean True, although in this
               | case we sometimes do need to rematerialize some definite
               | value into the register of choice.
               | 
               | In any case, it's mostly just a matter of correctly
               | writing the constant materializer; but if you adopt
               | multi-argument/multi-valued functions you simply never
               | encounter this problem:                   for arg, place
               | in zip(arg_list, arg_places):             load(arg,
               | place)         invoke(fun, kont)              for val,
               | place in zip(ret_values, ret_places):
               | load(val, place)         kontinue(kont)
               | 
               | Notice how degenerate loops simply disappear with no
               | additional handling.
        
               | formerly_proven wrote:
               | It doesn't make sense from a strict type and set theory
               | point of view because it doesn't make sense from a strict
               | type and set theory point of view. Neither C nor C++ are
               | rigorous languages.
               | 
               | We also have "void foo(void)" and here void takes on two
               | entirely different meanings, while type theory would
               | suggest this is a function that diverges if it were
               | called, which you can't.
        
               | masfuerte wrote:
               | It's not a function. A C function "returning" void is
               | just C's syntax for writing a procedure. C doesn't call
               | it a procedure, but that's what it is semantically.
        
               | moefh wrote:
               | The distinction between "function" and "procedure"
               | doesn't map very well to whether the return type is void
               | in C's syntax:
               | 
               | - On one hand, a lot of "functions" are actually
               | procedures that just happen to return a value: think for
               | example `write(2)`, which is clearly used for its side
               | effect, not to compute how many bytes could be written --
               | even though that's what it returns.
               | 
               | - On the other hand, you can have a "procedure" (i.e., a
               | function "returning" void) that actually has no side
               | effects other than storing a computed value in a
               | specified location (e.g. void square(int x, int *ret) {
               | *ret = x*x; }). That's clearly a function in the
               | mathematical sense, even though it "returns void".
        
               | simiones wrote:
               | C functions (and in fact the "functions" of most
               | mainstream programming languages) are not computations,
               | they are algorithms. An algorithm doesn't necessarily
               | have a result of any kind, at least not in the way that a
               | (mathematical) function has. The result of the algorithm
               | can be the state in which it leaves the World (e.g. an
               | algorithm for cleaning a house doesn't have a return
               | value, it changes the state of the house).
               | 
               | In fact the traditional programming name for what we
               | mostly call functions today was "(sub)routine" - you call
               | a subroutine, and when it finishes, it returns to where
               | it originally started.
               | 
               | Consider also that at the assembly level (and below it),
               | subroutines don't have return values, nor arguments. The
               | program counter simply jumps to the beginning address of
               | the subroutine, and the `return` instruction jumps back
               | to the address right after that jump. The subroutine may
               | read values from certain locations in memory, and
               | possibly write some others back, but none of this is
               | necessary or enforced in any way. C functions, and the
               | corresponding keywords, are much closer to this
               | conception of assembly subroutines than they are to the
               | mathematical notions of functions or computations.
        
               | MobiusHorizons wrote:
               | The calling convention does say where to look for the
               | return value. So in a sense the return value always
               | exists, but would not be meaningful if the function has a
               | void return type.
        
               | Measter wrote:
               | > (For what it's worth, if you use a prover-adjacent
               | language such as Agda or Idris, this is exactly how
               | things are going to work there.)
               | 
               | You don't even have to go that far, Rust supports this
               | concept. The built-in empty type is called `!`, and
               | cannot be constructed. It's partially unstable, and
               | there's a bunch of things you can't do yet, but you can
               | use it as a return type.
        
               | jerf wrote:
               | "If it appears as though it returns a member of [?], it
               | must hang/crash, because there are no members of [?]."
               | 
               | If you want to go with math, think more group theory. I'm
               | specifically thinking about how you can always create a
               | monoid if you have an associative binary operation,
               | because even if your associative binary operation doesn't
               | have an identity element, you can just _declare_ one.
               | Similarly, if you have  "functions" that "return
               | nothing", you just _declare_ that nothing right into
               | existence. Then you can just think of the C language
               | layer basically erasing away any attempt to examine that
               | value returned behind the scenes, because as you say,
               | why?
        
               | ackfoobar wrote:
               | > a prover-adjacent language
               | 
               | No need to go that far, you just need an ML inspired
               | language with subtyping.
               | 
               | https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-noth
               | ing... https://www.scala-
               | lang.org/api/2.13.6/scala/Nothing.html https://kotlinlang
               | .org/api/latest/jvm/stdlib/kotlin/-unit/
               | https://www.scala-lang.org/api/2.13.6/scala/Unit.html
        
               | bregma wrote:
               | The word "function" in mathematics and the word
               | "function" in C (and C++) are homonyms. Two words spelled
               | the same and pronounced the same but with entirely
               | different meanings. Any effort to conflate the two will
               | just end in tragedy.
        
         | Joker_vD wrote:
         | Because you can't use void in templates uniformly: you can't
         | have "T x;" when T = void, you can't do "return T{};", you
         | can't form types like "(*R)(int, T)", etc.
        
       | tpolzer wrote:
       | What's really weird to me is not that C++ has a unit type and
       | picked a weird name for it (that's just C++). The weird thing is
       | how many unit types it has:
       | 
       | - std::nullopt_t
       | 
       | - std::nullptr_t
       | 
       | - std::monostate
       | 
       | - std::tuple<>
       | 
       | And I'm sure there's more.
        
         | bombela wrote:
         | What about "void"?
        
           | tines wrote:
           | void isn't a unit type (inhabited by a single value), it's a
           | "bot" type, I.e. no values inhabit it.
        
             | jey wrote:
             | And "bot" refers to the bottom type:
             | https://en.wikipedia.org/wiki/Bottom_type
        
             | gpderetta wrote:
             | void is the unit type. The fact that it is not
             | constructible is a wart of the language, inherited from C.
             | It would be easy to fix and would simplify a significant
             | amount of generic code.
             | 
             | A function returning bottom cannot return, yet void foo()
             | {} can. In fact it can even return the result of calling
             | other void functions:                  void bar() {  }
             | void foo(){ return bar();}
             | 
             | In generic code void is usually internally replaced by a
             | proper, regular void_t unit type and converted back to void
             | at boundaries for backward compatibility.
             | [[noreturn]] void bar();
             | 
             | would be a candidate for a bottom-returning function,
             | except that [[noreturn]] isn't really part of the type
             | system.
        
               | tines wrote:
               | That's a good point. Maybe one could argue that rather
               | than the unit type not being constructible, the wart of C
               | is that functions that return "bot" can still "finish
               | executing without returning".
               | 
               | I would almost rather argue that void is indeed the "bot"
               | type, and a function marked with a void return type
               | shouldn't be said to "return void;" rather we should say
               | that it's an overloaded syntax that means the function
               | has no return value at all. Same for "return bar()"
               | there, that's just a false-friend of the syntax for
               | returning a value, just syntactic sugar for "bar();
               | return;".
        
               | MathMonkeyMan wrote:
               | I ran into this recently writing some C++20 coroutines.
               | The protocol for delivering values from a coroutine that
               | was previously suspended has two flavors: one for values
               | and one for void. My initial draft just implemented the
               | value version and used a struct VoidTODO {} where void
               | should be.
               | 
               | It's too late now. void pointers are used as a pun to
               | mean "type wildcard." If void were a real thing that
               | could have a size and address, that wouldn't work
               | anymore.
        
               | rerdavies wrote:
               | Yes!! Been there done that. Two flavors of EVERYTHING:
               | one to deal with functions that return values; and one to
               | deal with void functions. It's awful.
        
             | kazinator wrote:
             | void is not a subtype of all types, though.
             | 
             | C and C++ don't have a type spindle, where void would be at
             | the bottom. Only C++ has the concept of subtype, only in
             | the class system, and the C++ class system doesn't have a
             | bottom type; there is no bottom class that is a base for
             | all the others.
             | 
             | void is not a proper type; it's just a hack shoehorned into
             | a convenient spot in the type system.
             | 
             | Which is why the C++ people have to invent this whole zoo
             | of other things.
             | 
             | If void were a type, then, for starters, "return x;" would
             | be syntactically valid in a function returning void. (Only,
             | no possible x would satisfy the type system, so there would
             | have to be a diagnosable rule violation in that regard.)
             | 
             | A function returning void does not return a type. It
             | doesn't return anything; it is a procedure invoked for side
             | effects.
             | 
             | The same situation could be achieved in other ways, like
             | having a _procedure_ keyword instead of _void_.
             | 
             | The (void) parameter list is another example of _void_ just
             | being a hack. It was introduced in ISO C, and then C++
             | adopted it for compatibility.
             | 
             | The 2023 draft of ISO C finally made () equivalent to
             | (void), though it will probably take many decades for
             | (void) to disappear.
        
               | gpderetta wrote:
               | > C and C++ don't have a type spindle, where void would
               | be at the bottom. Only C++ has the concept of subtype,
               | only in the class system, and the C++ class system
               | doesn't have a bottom type; there is no bottom class that
               | is a base for all the others.
               | 
               | A bottom type is not the base of all other types.
               | 
               | > void is not a proper type; it's just a hack shoehorned
               | into a convenient spot in the type system.
               | 
               | It is a type, but it is not Regular and it is incomplete.
               | 'return x;' is invalid in a void-returning function
               | because it doesn't type check. 'return void()' or 'return
               | (void)0;' or 'return void_returning_function();' are all
               | valid because they type check.
               | 
               | Making void regular has been proposed multiple times [1].
               | It is a relatively simple extension but nobody that cares
               | has the time to carry it through standardization.
               | 
               | [1] https://open-
               | std.org/JTC1/SC22/WG21/docs/papers/2016/p0146r1...
        
               | kazinator wrote:
               | In C, it is not like that. From the 2023 draft:
               | 
               |  _6.8.7.5 The return statement_
               | 
               |  _Constraints_
               | 
               |  _1 A return statement with an expression shall not
               | appear in a function whose return type is void._
               | 
               | It's a constraint violation. It doesn't matter what the
               | type of the expression is.
               | 
               | It looks as if C++ made a small improvement here.
               | 
               | Yes, the bottom type is at the bottom of the type
               | derivation hierarchy. That's why the word bottom is
               | there; that's what it's at the bottom of. It's also why
               | it can't have any instances. Since every other type is a
               | supertype, then if the bottom type contained some value
               | V, that value would be imposed into every other type! V
               | would be a valid String, Widget, Integer, Stream, Array
               | ... what have you.
        
         | omnicognate wrote:
         | The distinct types are the whole point. You wouldn't want a
         | std::tuple<> to be implicitly convertible to a std::optional<T>
         | (for arbitrary T), and std::nullptr_t exists to be the type of
         | nullptr, which captures the conversion behaviours appropriate
         | for null pointer literals and has nothing to do with the
         | variant use case std::monostate exists to serve.
        
           | tpolzer wrote:
           | If there was a std::unit_t and it was implicitly convertible
           | to optional, tuple and pointer, I don't think that would be
           | worse in terms of usability at all (maybe worse in
           | readability for people who haven't heard of a 'unit' type).
           | 
           | As for the std::variant use case, using std::monostate is
           | only a matter of convention there. You could use any of the
           | other unit types just the same.
        
             | omnicognate wrote:
             | std::monostate is explicitly provided for use with
             | std::variant. It's in the <variant> header. Sometimes
             | people use it for other things, but that's really an abuse,
             | especially given defining your own type suitable for such
             | cases is typically as simple as `struct mytype{};`.
             | 
             | Using one type to represent empty literals for optional,
             | tuple and pointer types, implicitly convertible to all of
             | them, would make the compiler accept many obviously
             | accidental constructs. In a world where the maintainers of
             | C++ are trying their hardest to make the language _safer_
             | what conceivable benefit would there be?
        
             | gumby wrote:
             | Then you're basically back to "anything can convert back
             | and forth with void _" -- the point is to _ avoid* that.
        
       | ape4 wrote:
       | Good thing they made this instead of expanding the standard
       | library to be more like Java's or Python's. It still only
       | contains the most basic functions, and std::monostate ;)
        
       | leecommamichael wrote:
       | Raymond is a very smart and productive person, and is not
       | maligning C++ at all in this article. It makes me want to
       | reassess my bittersweet perspective on the language.
        
         | doctorpangloss wrote:
         | On the flip side, it shows why so many people are excited about
         | Rust. You can pick up literally any valuable thing off the
         | ground that made the mistake of being built around C++, like
         | CUDA, slap it on Rust, and people will both adopt it and be
         | excited to contribute to it.
        
       | furyofantares wrote:
       | Gerard: But it doesn't _do_ anything!
       | 
       | Hans: No -- it _does_ nothing.
       | 
       | https://scryfall.com/card/wth/154/null-rod
        
         | gpderetta wrote:
         | No no, that's a void _pointer_ .
        
       | nostrademons wrote:
       | A "monostate" in the design-patterns lingo of 20 years ago was a
       | class with only static member variables, basically where all
       | state is shared state. It was supposed to be alternative to the
       | Singleton pattern where you don't need all those .getInstance()
       | calls and instead can just default-construct an instance, any
       | instance, to get access to the shared state. It fell out of favor
       | because usage was fairly error-prone and surprising to
       | programmers who are not familiar with the pattern. Most people
       | expect that when you create a new instance, you are actually
       | creating new state, but monostate intentionally makes each new
       | instance just a window on the shared global state.
       | 
       | I would've thought that the C++ template class would be just a
       | marker interface to use on a monostate, so that users of the
       | class _know_ that it has shared state. But it seems like usage
       | patterns in the article are very different from that, and all the
       | comments here are ignorant of the history of the monostate
       | pattern and befuddled at its intended usage. Maybe it was added
       | to the standard by someone familiar with the design pattern, but
       | they didn 't do a good job with education and documentation to
       | explain to everyone else what it was for?
        
         | quietbritishjim wrote:
         | That just sounds like a different thing with the same name.
         | It's nowhere close to the purpose of std::monostate.
        
       | kazinator wrote:
       | It's poor name. If it has no members, it holds no bits. Therefore
       | it has no state. It's not enough for an object to exist in order
       | to hold state. It must be capable of distinguishing at least
       | between two values, like true or false. If something doesn't hold
       | state, the word _state_ has no business in its name.
       | 
       | This thing is just a counterpart to _void_ that is a class. A
       | better name would be _voidclass_ or something along those lines.
       | 
       | (There is a Monostate pattern, but that involves a class with
       | state: just all the state is static. It's basically like a module
       | with global variables. Completely different thing.)
        
         | binary132 wrote:
         | "True" and "False" sound like two states. This is just one. :)
        
           | kazinator wrote:
           | Yes, exactly like 640K sounds like a power of two, if you're
           | a MS-DOS user.
           | 
           | But in a way, this is right since:                    <no of
           | bits>         2                = <no of states>
           | 
           | When the number of bits is 0, 2^0 = 1: 1 state. A state
           | machine with one state is certainly possible.
           | 
           | Problem is we need 2 or more states to do anything useful
           | with state. We can draw an initial state bubble in a state
           | diagram and not add any states; it can even have transitions
           | back to itself.
           | 
           | So maybe monostate is not exactly a misnomer; it's just weird
           | to mention state about something that is not useful for
           | working with state.
        
       | JackYoustra wrote:
       | There's a fun thing like this in Swift too! `Void` is an empty
       | tuple, and has all of the related constraints (can't conform to
       | protocols, being the most salient one). If you have a type that
       | has to conform to, say, `Equatable` or `Codable` you should
       | instead use `Never?` which you can conform to most protocols via
       | throwing `fatalError` on an extension of `Never` to the protocol.
       | 
       | Anyway, I write basically this on my blog for a more thorough
       | explanation: https://www.jackyoustra.com/blog/non-equatable-void
        
       | shadowgovt wrote:
       | So... Isn't the fact that you can't default-construct that
       | variant without `monostate` working-as-intended?
       | 
       | Not everything can or should be default-constructed or default-
       | constructible. That does complicate initialization sometimes
       | (i.e. there are practical reasons to "suspend" construction until
       | you have all the needed data), but you're not avoiding breaking
       | type safety by adding a `monostate`, you're just giving yourself
       | the "could be null" headache.
        
       | vasilipupkin wrote:
       | this is crazy confusing stuff that should be banned by most
       | sensible internal coding standards.
        
       ___________________________________________________________________
       (page generated 2024-07-22 23:08 UTC)