1. 17

    However, it also defines operator* and operator->. These methods also provide access to the underlying T, however they do not check if the optional actually contains a value or not.

    Wat?

    1. 15

      Using operator* or operator-> on an optional that doesn’t contain a value is undefined behavior, see: spec, cppreference

      I’ve been about to migrate a bunch of APIs from std::unique_ptr<T> to std::optional<T> and now I’m really not feeling as great about that plan.

      1. 19

        On the face of it this seems like an insanely bad API design decision by the committee.

        1. 5

          What should it do? Throwing exceptions would make the API useless to many large consumers. Default-constructing the value type would probably be worse. The C++ type system just isn’t very good.

          1. 25

            What should it do?

            not offering those operators at all and instead forcing the usage of map, flatmap or get_or_default like most languages do.

            We opensourced a library that implements a more functional API here

            1. 2

              What should it do?

              not offering those operators at all and instead forcing the usage of map, flatmap or get_or_default like most languages do.

              That’s unacceptable when you know the optional has a value and performance is critical. Nothing is stopping you from implementing “get_or_default” on top of the operations provided, but you cannot implement operator* as it currently is on top of “get_or_default”

              1. 4

                That’s unacceptable when you know the optional has a value and performance is critical.

                C++ is a zero cost abstraction language and lambdas get inlined. Unless the optional is implemented via a vtable, you should not see any overhead.

                1. you might be right today but what about tomorrow? - will your assumption still be true if someone else will modify some (distant) part of the codebase?

                2. If you know that an optional will always contain a value, why is it in an optional in the first place?

                Nothing is stopping you from implementing “get_or_default” on top of the operations provided, but you cannot implement operator* as it currently is on top of “get_or_default”

                Well the argument is operator* you should not exist as it eventually leads to unsafe code and bugs. You may always know when you can get away of using operator* without prior checks and may not need such construct but most people get it wrong at least once.

                If you work in a team you will realize those unsafe usages become a liability as they are prone to break due to distant changes. As a result this is not very social: this practice pushes the responsibility of debugging or verifying your assumptions to everyone who is contributing to the codebase. Every Time someone works on a feature and coincidentally breaks the unsafe code will also need to fix it up; it becomes his responsibility.

                Lastly, just for the sake of argument: In case your optional implementation does not include a footgun, this is how you could build one:

                T const& optional<T>::operator*()const {
                   return this->get_or_default(*(T*)nullptr);  // that's fine it's just undefined behaviour 
                }
                
                1. 2

                  You shouldn’t be forced to check if the option is filled each time you access it. One check is enough, then you can assert that the option is filled in all following code paths. If you don’t want to do that, force your team to always use “get_or_default” but don’t force the world to work like your team, not everyone has your performance constraints.

                  1. 1

                    You shouldn’t be forced to check if the option is filled each time you access it.

                    You only test once. If you are worried that running map or get_or_default test for the content multiple times, don’t worry, the compiler will optimize it away: https://godbolt.org/z/7RGUCf

                    1. 1

                      That’s not true in all cases.

                2. 2

                  If you know a value exists and performance is critical …why are you using std::optional in the first place?

                  By this logic, you might as well demand that std::map should have implemented operator[] not to take const Key& key but instead an already hashed bucket identifier, and if there’s nothing in that bucket, or not what you were expecting, oh well, have some undefined behaviour?

                  It’s silly. If you don’t want the overhead of a particular container, don’t use that container.

                  Insisting that std::optional container has to have a hole in it … either you want an Option or you want a raw pointer. “I want an Option but it has to behave exactly like a bare pointer” is an incoherent position for an API trying to satisfy everyone and ending with something useful to no one. “It’s safe as long as you always 100% without fail manually check it before dereferencing, otherwise undefined behaviour” adds nothing that wasn’t already available.

                  1. 2

                    If you know a value exists and performance is critical …why are you using std::optional in the first place?

                    What if you already checked it once? Why should you be forced to check each time you access the contained value?

                    Insisting that std::optional container has to have a hole in it … either you want an Option or you want a raw pointer.

                    It’s not a raw pointer, it’s a boolean paired with a value. There’s no necessary indirection.

                    1. 2

                      What if you already checked it once? Why should you be forced to check each time you access the contained value.

                      Then extract the value the first time? Why would you design an entire API around “people might unnecessarily check for whether some value is present multiple times”? Leave Nones at the edges of your program. Once you’ve determined that there is Some(x), everything else should be dealing with x, not the Option it started in.

                      The question, if anything, illustrates exactly why these are bad operators to have in the API.

                      Without them, Options are cumbersome enough that you very quickly realize slinging them around in the core of your program is silly; you extract the present value, substitute a default, or deal with an unsubstitutable None at the edge, and design everything else in terms of the value.

                      With these operators apparently a nonzero subset of people think “I should just leave the value in the Option and touch its raw memory because “I know” I checked for presence once upstack”. Which works until someone refactors something and suddenly there’s a codepath that pulls the trigger on your footgun.

                      Good API design guides the user towards good usage. This clearly doesn’t.

                      It’s not a raw pointer, it’s a boolean paired with a value. There’s no necessary indirection.

                      It’s a raw pointer (T*) you’re getting back in the case of operator* and operator->, the operators this entire thread of posts is discussing. If you’re going to blindly call those you could equivalently have just slung around raw pointers wherever you acquired the option to begin with.

                3. -1

                  That’s not idiomatic C++.

                  1. 7

                    Could you clarify what you mean by idiomatic? What design would be both idiomatic but not permit dereferencing a null/uninitialized pointer?

                    1. 23

                      dereferencing a null/uninitialized pointer is idiomatic C++

                      1. 3

                        and segfaults…

                        It’s true that nullpointers may be valid addresses on some architectures but we should always model the semantics correctly in our programs.

                        But what does not make sense is dereferencing an empty optional/smart-ptr. If a null-pointer is a valid and intended value than you should assign it to eg. your optional/smart-ptr.

                4. 9

                  I mean, the semantics of Option in the abstract just aren’t safely compatible with an operation that blindly assumes Some(x) is present and directly grabs x’s memory, so the choices are either:

                  1. use exceptions to avoid unsafe accesses at runtime, which as you say, not every codebase will appreciate

                  Or

                  1. just don’t offer those operators on Option. They fundamentally compromise the entire point of the type. If you want to make blind assumptions about memory being initialized, use a raw pointer; Option should enforce its safety benefits.
                  1. -3

                    That’s not really how C++ works.

                    It offers those operators, you can use them if you want to, and if you don’t want to (which you clearly don’t) then don’t use them.

                    1. 13

                      It offers those operators

                      It offers implementors the choice to implement those operators on their types. There is absolutely no requirement that all types do so, let alone do so in a way that provides unsafe semantics. Their presence on std::optional makes little sense given the semantics of Option as an Abstract Data Type

                      1. 2

                        Feels like Java 8’s Optional type all over again.

                        1. 2

                          As bad as Java 8’s Optional#get is, it at least always aborts control flow via an exception if an empty value is forced unwrapped.

                          From what I can see, the undefined behaviour of C++ theoretically allows an implementation to not abort control flow and perhaps use zero values or something, which makes reasoning about control flow invariants in portable code harder.

                          1. 1

                            Why is Java’s Optional bad? Having used both Java and Rust, the only thing I really miss is pattern matching, which is a separate concern from the type itself.

                            (I already know about the complaint that the optional itself can be null, but… baby steps, I guess.)

                            1. 2

                              “Bad” is honestly too strong a word for it; it got a lot right.

                              That said, it gave a force-inwrapping operation the name get which is both too easy to discover and doesn’t signify the exceptional case well enough. A member of the JDK team admitted it was a mistake which is why it’s being deprecated in a future JDK for a more verbose name.

                              The way it handles nulls is also annoying. Not because optionals can themselves be null like any other object, but because returning null in areas like mapping lambdas implicitly converts into empty optional rather than a present optional of a null. While getting rid of nulls like this seems like a good idea initially, it breaks the monadic laws which breaks composability in places.

                              This article from Atlassian details the problem: https://blog.developer.atlassian.com/optional-broken/

                              1. 1

                                Eh. I think of the Java 8 stuff as occupying an intersection of two similar languages that don’t share all of their idiomatic patterns. I know that winds up resulting in yet more inconsistencies in spots, but I don’t know how much that bites people in practice.

                                Then again, I acknowledge most of the stuff I’ve been working with is greenfield— there’s nothing to accidentally refactor the wrong way.

                      2. 1

                        It offers those operators, you can use them if you want to

                        meaning it offers you the option to introduce bugs to your codebase if you want to?

                    2. 3

                      What should it do? Throwing exceptions would make the API useless to many large consumers.

                      And undefined behavior makes it usable?

              1. 1

                If it’s orthogonal to build systems, how is it really a ‘C++’ dependency manager? And why not just use a ‘language-agnostic’ dependency manager, such as Nix, which will happily fetch either source or binary dependencies, including a C++ compiler, and also exists.

                1. 1

                  Unfortunately, C++ Libraries don’t not compose arbitrarily. The ABI of a C++ Library depends on many factor:

                  • compiler
                  • language standard (C++11 and onwards use a different string implementation for instance)
                  • exception support
                  • Runtime type information support
                  • architecture specific options
                  • non-standard optimizations (eg. -ffastmath, -fomit-frame-pointer)
                  • symbol visibility options (eg. -fdefault-visibility=hidden)
                  • threading related flags (some libraries provide a thread safe implementation only if -pthread)

                  Furthermore many libraries are “configurable” (OpenCV has around 200 different options). Every option may affect the public API and the ABI of a library.

                  Given the amount of options binaries become a gamble (is the binary using the exact combination of options I need?)

                  If you attempt to build from source you need to find the set of compiler-flags that is compatible with all your (transitive) dependencies. This is an SAT problem in itself.

                  Once this is done you still might hit problems like symbol collisions that you need to resolve by isolating libraries.
                  (eg. the r3 library ships with a different malloc implementation and is in conflict with another libraries malloc).

                  Isolation can be accomplished by manual name-mangling, or by using a version-script and compilation into a shared library.

                  The fact that (transitive) dependencies affect the way you build your application makes package management not orthogonal

                  1. 1

                    Given the amount of options binaries become a gamble (is the binary using the exact combination of options I need?)

                    This is not a problem in Nix, because it each ‘output’ is given a unique hash based on its inputs, so if you build a package with a different compiler, or on a different platform or with different configuration options, you will get a different hash and Nix won’t consider the resultant binaries a valid substitution for anything which depends on a different set of inputs.

                1. 26

                  The comparison seems a bit forced. C++ is an extreme case of a language with generics. There are languages with generics that are quite compact.

                  1. 10

                    Eiffel comes to mind.

                    1. 7

                      Comparing generics in other languages to templates in C++ is comparing apples to ham burgers. I wouldn’t call templates “generics”. C++ templates are much more powerful. How powerful are C++ templates? Well, they are Turing complete…

                      1. 2

                        C++ templates turned into sth. noone has ever really planned. Many features Templates have are totally incidental.

                        Templates have some very interesting interactions with other language features. Used in a creative way you can make pure compile-time functions return different values each time:

                        int main () {
                          int constexpr a = f ();
                          int constexpr b = f ();
                        
                          static_assert (a != b, "try again");
                        }
                        

                        http://b.atch.se/posts/constexpr-counter/

                        1. 1

                          now THAT’S some black magic right there.

                      1. 14

                        INI does not support hierarchies.

                        1. 10

                          INI files have no well-defined spec, and very few parser implementations work the same way.

                        1. 3

                          Was intrigued by Buckaroo and we’ve had several threads on it, starting with: https://lobste.rs/s/3rqvx4/approaches_c_dependency_management_why

                          Anybody with a recent update on how they feel about Buckaroo?

                          1. 3

                            Buckaroo is actively being developed. Our current challenge is keeping packages up-to-date and improving Buckaroo itself.

                            Although Buck(aroo) could just just invoke the underlying build system we discovered that this is not sufficient for several reasons:

                            • most buildsystems are not reproducible
                            • have insufficient support for cross-compilation
                            • don’t play nicely with other libraries for various reasons (that are usually avoidable)

                            In order to resolve those issues, we started porting all our libraries to Buck. As this turned out to be a big undertaking, we started investing our time into automated tooling: Have a look at our blogpost about buildinfer or it’s website.

                          1. 5

                            A nice resource I’ve been comming back to over the years is: https://www.euclideanspace.com/maths/algebra/clifford/index.htm

                            It gives you a pragmatic introduction to Geometric(clifford) Algebra.

                            Furthermore I’d like to mention David Hestenes. He has been pushing Geometric Algebra for years. He’s written many books and papers on this topic. He shows that physics (education) could be simplified by using Geometric Algebra.

                            An interesting extension si Conformal Geometric Algebra. It allows you to describe shapes and the geometric product between shapes gives you the intersection shape.

                            There are even couple C++ libraries: http://versor.mat.ucsb.edu/ http://www.cs.uu.nl/groups/MG/gallery/CGAP/index.html

                            1. 1

                              http://www.geometricalgebra.net/ may be of interest as well. One can probably pick up a used copy of the text fairly cheap.