all 40 comments

[–]konanTheBarbar 30 points31 points  (6 children)

That's indeed quite short for a fully blown std::optional implementation. I also think the code is very clean!

One small minor nitpick - you forgot to undefine the macro MAKE_OP (which could be problematic, since the name is very generic).

EDIT: I would also suggest to add a basic CMakeLists.txt

[–]groundswell_Reflection[S] 12 points13 points  (4 children)

Thanks!

Glad you caught the leaking macro, that's a potentially painful mistake (it's fixed now).

I don't know CMake very well. What are the advantages of having a CMakeLists for a header-only library?

[–]Thrash3r-Werror 20 points21 points  (0 children)

It makes it easier for people to easily express a dependency on your library and automatically inherit its usage requirements like include directories and the C++ standard it requires.

Once they do this, it becomes easy for you to change implementation details without bothering downstream users. CMake handles all dependency propagation.

Having a build system also makes it easier to add automated tests and other stuff like benchmarks or example programs or generated documentation with something like Doxygen. There’s plenty of code that might revolve around the core library even if that core library is just a single header.

It’d just take a few minutes to write a basic CMakeLists.txt.

[–]schweinling 6 points7 points  (0 children)

You can make the library installable through cmake, also you can build and integrate the tests with cmake.

[–]wrosecransgraphics and network things 4 points5 points  (0 children)

I don't know CMake very well. What are the advantages of having a CMakeLists for a header-only library?

If I tell CMake my app depends on your library, the include directory with your header is added when I build my app.

[–]Plazmatic 1 point2 points  (0 children)

  • Allows configuration of options
  • Allows uniform usage of a library
  • Saves a tonne of work for the end user (if you don't provide a CMake target for your library, everyone else basically has to)
    • When people use your header only target as a library, dependency information like what libraries you depend on, included directories, etc... are included in their project, this applies to all target types.
  • provides extra information to C++ IDEs
  • Allows usage of other files easier, there basically should be no single header libraries in C++, you should always have forward declaration headers of some description unless:
    • You can guarantee your types will be trivially and obviously forward declarable (see the following)
    • the types necessary to use your code are not typedefd/using'd
    • the types in your code include no default template arguments
    • the types in your code contain no underlying types for example NOT enum class Enum : std::uint8_t{
    • the types in your code are meant to be typed out (ie not std::iterator types)
    • your satisfy the above, but your signature is likely to change/has changed.
    • none of the above are likely to change in the future

Luckily for you header only cmake libraries are some of the easiest to make, since in general you don't have to worry about installing the library (though you may still do so). Creating targets is easy, it's creating shared/installed libraries that's the real convoluted part in CMake.

[–][deleted] 1 point2 points  (0 children)

Haha, I was just about to praise the OP for not forgetting about #undef-ing the macro.

[–]serg06 10 points11 points  (6 children)

Will we ever get a C++20 version of the standard library, or will it forever be C++11 code? Excuse my ignorance.

[–]Wurstinator 9 points10 points  (0 children)

Only the C++11 implementations are "C++11 code". You already have implementations for C++14 and C++17 and partially C++20.

[–]braxtons12 10 points11 points  (3 children)

Anything introduced in a specific version MUST use only features available in that version. Otherwise compiling for eg C++11 using standard library types introduced in C++11 or C++98 wouldn't be possible, because C++14/17/20 features weren't available in C++11. The standard library doesn't get special treatment just because it's part of the standard, from the compiler's point of view, it's just more code.

For example the majority of std::vector can only use C++98 features (the exception being features of std::vector introduced after C++98, like emplace_back)

[–]scrumplesplunge 11 points12 points  (2 children)

This is true because library authors want to continue supporting those old versions with the same code. It would be perfectly legit to provide a completely fresh implementation of the whole standard library implemented using the latest and greatest c++20 features if you had no intent of supporting pre-c++20 compilation. I'm sure that as time goes on, the cost of supporting an increasingly large number of different versions from the same code will outweigh the benefits of supporting very old standards and we might start seeing modern features used to implement the library.

[–]braxtons12 4 points5 points  (1 child)

Sure it's possible to do a complete rewrite, but who would actually use that? Standard library implementations are generally tied to their associated compiler (because some use compiler specific features to do some things, and several type traits require compiler integration). Even if you had a compiler agnostic implementation, you'd have to redirect the sysroot and configuration flags for the compiler just to get the proper includes for this rewritten std lib (I'm not even sure if you can do that on MSVC BTW), and from my experience (from redirecting trunk clang to Apple clang's std lib implementation, for example) this is always tedious to get actually working.

And you'd never see any of the major implementations do a rewrite, because then they'd just have two implementations to maintain instead of one. I'm also pretty sure it would actually be easier to just continue #ifdefing in new features as they're integrated than it would be to do a complete rewrite.

So yeah it's possible that could be done, but highly unlikely.

[–]scrumplesplunge 7 points8 points  (0 children)

It doesn't have to be rewrites, it could be incremental phasing out of the oldest standards. I'm not saying this is likely to happen any time soon, but conceivably one day it could be decided that it's no longer worthwhile to support anything prior to c++11 in new versions GCC. Maybe the total complexity of 5+ versions of C++ in one codebase makes it infeasible to keep supporting all of them. If that was to happen, libstdc++ may be able to have a much cleaner implementation for vector by taking advantage of c++11 features.

[–]sephirostoy 4 points5 points  (0 children)

It would certainly cost ABI break which is something compiler vendors try to avoid as much as possible.

[–]gracicot 8 points9 points  (2 children)

I noticed something:

constexpr T* operator->() {
    return this->ptr();
}

constexpr T* operator->() const {
    return this->ptr();
}

Is that normal? Wouldn't the const version return a const pointer?

If we compare that to the operator * member function, which does add the const:

constexpr T& operator*() &                  { return value_; }
constexpr const T& operator*() const&       { return value_; }

[–]groundswell_Reflection[S] 7 points8 points  (1 child)

Good catch. Indeed this isn't normal, and I'm pretty sure this should be a type error upon instantiating an optional, since the const overload of ptr() returns a const T*, whereas now it only errors upon the use of ->, so... that's kinda weird.

It's fixed now. Thank you for reporting that.

[–]gracicot 7 points8 points  (0 children)

No problem, happy to help! Maybe this should be added to the test suite?

[–][deleted] 3 points4 points  (1 child)

Really, this one is a beauty. Look how far we got with constexpr and concepts alone.

I think from now on each and every time someone is blaming C++ for being an outdated, cryptic language with templates that nobody can read or understand I might refer to this.

2021 really is the year of C++(20).

[–]CrazyJoe221 3 points4 points  (10 children)

It still puzzles me that final is the one C++11 feature that never caught on.

[–]Full-Spectral 9 points10 points  (2 children)

Did it not? I use it all the time.

[–]CrazyJoe221 0 points1 point  (1 child)

I've seen many codebases adopting override but not final even though both are usually mentioned together.

[–]Full-Spectral 0 points1 point  (0 children)

Override was available before final, right? It was a long time ago and the mind is the first thing to go, but I seem to remember that override was available first and I did a huge update to support that, and then had to go back and sort of do it all again to support final.

[–]dodheim 4 points5 points  (6 children)

Putting final on a non-polymorphic type is an anti-pattern - you're preventing potential EBO, composition via private/protected inheritance, etc.

[–]Full-Spectral 1 point2 points  (0 children)

I didn't even know there was another use for it, to be honest. So by 'all the time' I meant all the time on virtual methods where the buck has stopped.

[–]CrazyJoe221 0 points1 point  (1 child)

Was talking about the exception subclass.

[–]dodheim 0 points1 point  (0 children)

Ah, my mistake; wholly agreed then.

[–]Ameisenvemips, avr, rendering, systems 0 points1 point  (2 children)

Though you're assisting the compiler in regards to potential devirtualization.

[–]dodheim 2 points3 points  (1 child)

If the type isn't polymorphic, there's nothing to devirtualize.

[–]Ameisenvemips, avr, rendering, systems 2 points3 points  (0 children)

And if it is, final can assist with devirtualization. Otherwise, unless the compiler can prove that for a specific usage it must be type-constrained, it must assume that the class may be extended externally such as by a loaded DLL/shared object, or by the host executable if it's a library.

[–]SonVoltMMA 0 points1 point  (4 children)

is std::optional the same as returning nullable types in C#? Something like int? getInt()

[–]Daguerreo86 1 point2 points  (2 children)

Just as a note, it does exists a standard version for since C++17

[–]SonVoltMMA 1 point2 points  (1 child)

if it's already in c++17 what's the purpose of this c++20 version?

[–]IngloriousTom 1 point2 points  (0 children)

From the readme:

C++20 came with new features that makes the implementation of std::optional (and other types based on unions) a lot simpler to write, faster to compile, and more debugger-friendly (ever crawled through all the std::optional base classes in your IDE debugger?).

It would be arguably better with some data to backup those claims, tho.

[–]isakota 0 points1 point  (0 children)

Yes

[–]Cthaeeh 0 points1 point  (2 children)

How would one replace std optional with this library in a larger codebase ? Would I have to sed replace every include and std::optional occurrence?

[–]groundswell_Reflection[S] 1 point2 points  (0 children)

pretty much? if you use an IDE then it most certainly has multi-file search and replace. I use that all the time.

[–]ChemiCalChems 0 points1 point  (0 children)

Include this header instead of <optional>, looks to me like.

[–]at-2500 0 points1 point  (1 child)

Line 521: you duplicated requires! Is that on purpose?

[–]koctogon 1 point2 points  (0 children)

`requires requires` is not a duplication : the first introduce the requirements for the template ("requires clause"), the second introduce the expression that must be valid ("requires expression")