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.

              2. 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

                  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. 11

                      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.