- 04 Jan, 2026 *
In my current project I’m reimplementing a lot of functionality provided by the C++ standard library. Mostly for fun, but also to learn about what lies under the hood of the structures and functions that I regularly use. So I decided to write a couple of blog posts documenting this journey. And to start I chose to go with a structure so useful when the result of the function is not guaranteed - std::optional.
Table of contents:
*Full source code can be found [here](https://github.com/anu…
- 04 Jan, 2026 *
In my current project I’m reimplementing a lot of functionality provided by the C++ standard library. Mostly for fun, but also to learn about what lies under the hood of the structures and functions that I regularly use. So I decided to write a couple of blog posts documenting this journey. And to start I chose to go with a structure so useful when the result of the function is not guaranteed - std::optional.
Table of contents:
Full source code can be found here.
Expectations
In these posts I don’t plan on making my implementations fully compliant with the C++ standard - it’d be too tedious and frankly unnecessary for my case. Instead, I’ll look at how the thing is commonly used, what functionality is expected from it, and try to satisfy that.
That said, let’s look at std::optional. I mostly use it as a return type for functions where the presence of the return value is not guaranteed:
auto AsInt(const std::string& str) -> std::optional<int>
{
try { return std::stoi(str); }
catch(...) { return std::nullopt; }
}
if (auto val = AsInt("123")) std::cout << *val; // prints 123
if (auto val = AsInt("hello")) std::cout << *val; // doesn't print
Sometimes I also use it to signify that some field in a structure might not be present:
struct Person
{
std::string name;
std::optional<std::string> middleName; // not everyone
// has a middle name
};
From these short examples we can see certain qualities our implementation should satisfy. It should:
- work with any type,
- be implicitly constructed from said type,
- be able to convert to
bool, - provide a way to get the actual value,
- provide some way to say "there’s no value" (
std::nulloptin the case of the standard library).
With these in mind let’s move on to the implementation. In my code I’ll be using the name Opt instead of optional since I’ve lately become fond of concise names, but that shouldn’t be an issue.
Naive implementation
Our first thought could be to make a structure like this:
template<typename T>
class Opt
{
T value;
bool hasValue = false;
public:
// public API
};
It consists of a value that can be optionally stored, and a hasValue flag to show if the value is present.
This code, however, has a significant drawback: it tries to construct the value even if it is not present. It’s not a big deal if it’s something like an int or a simple structure, but if the constructor is complex and has side-effects (e.g. is used for static initialization), this becomes a problem. Also, if the default constructor is not present at all, something like this won’t compile at all:
Opt<Test> o;
// error: no matching function for call to 'Test::Test()'
To fix this we need to find a way to delay the initialization of the value field.
Delayed initialization
The preferred way to do this post-C++17 is to use the std::optional for the field. Unfortunately, we’ve voluntarily put ourselves into the pre-wheel era, so we have to look further.
We could make it a pointer, either raw or smart one. This would work, since the pointer can be initialized at any point after the object itself was created. However, adding a level of indirection seems like an overkill for simpler Ts.
Another way is to allocate memory for the member upfront and call the constructor later. This could be achieved with something like
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage;
// alternatively
alignas(T) std::byte storage[sizeof(T)];
// alternatively to the alternative
std::byte storage[sizeof(T)];
Here we reserve the memory for a member of type T. Later we can call a constructor with placement new to construct the object in the storage. This would also work, but it adds plenty of complexity, since we now need to manually control the bytes and alignment, rely on reinterpret_cast and so on. If only there was a way to make the compiler do it for us...
And what do you know, there is one in the face of a union. If we refer to the ever-so-helpful cppreference, we can see that "The union is at least as big as necessary to hold its largest data member", and "The lifetime of a union member begins when the member is made active.". That means that if we put our T value from before inside a union, the compiler will:
- control the size of storage for us,
- not actually construct the member unless we set its value.
We’ll still need to control proper construction/destruction, and make sure not to access an uninitialized value, but I feel like it’s manageable. The main problem this approach introduces is that your IDE will probably scream at you about having an uninitialized member, but we’ll politely ignore its tantrums and proceed.
Improved implementation
With our new handy union, the class now looks like this:
template<typename T>
class Opt
{
union { T value; }; // specifically, I'm using an
// anonymous union here
bool hasValue = false;
private:
auto Destroy() { if (hasValue) { value.~T(); hasValue = false; } }
public:
constexpr Opt() { /* empty constructor */ }
constexpr ~Opt() { Destroy(); }
// the rest of the public API
};
Notice that the Opt() constructor is created as explicitly empty. If I were to write Opt() = default;, the default compiler-generated constructor would’ve tried to default-initialize all members of the class (including value) with some value. This would: 1) be undesirable, and 2) break if the T were non-trivial.
The destructor calls the private Destroy() method which in turn manually calls the value’s destructor. I’ve put it in a separate method since we’ll need it later.
NullOpt
Before we look at the rest of the constructors and operators, we need to satisfy another requirement we set for our optional. That being the "no value present" value. In the standard library we have nullopt for this purpose. It is a constant object of the nullopt_t, which is a tag type, meaning its only purpose is to show that optional has no value. It’s defined like this:
struct nullopt_t
{
constexpr explicit nullopt_t(int) {}
};
As the note at cppreference says, the constructor is there "to support both op = {}; and op = nullopt; as the syntax for disengaging an optional object.". And this is not a default constructor to prevent ambiguity in the case of brace-initialization. This is understandable, and we’ll also support the {} syntax for disengaging the object, but in a different way.
So for our purposes we can define the structure as follows:
struct NullOpt {}; // no constructor
inline constexpr NullOpt nopt;
Rest of the API
The convenience of std::optional is partly in the ways it can be constructed, those being almost all the ways. This way we can assign values, return values from functions, etc. without wrapping them in optional{val}.
To accommodate for such syntax we’ll provide a bunch of copy- and move-constructors and assignment operators. One thing we need to keep in mind is the way we construct the value in our union. If we just did value = newValue, it would work only for trivially constructed types, not for anything more complex. To solve this, we can use placement new specifying value as the memory we construct the object in:
new(&value) T(newValue);
We also need to appropriately Destroy() the value when assigning empty optionals or moving from other optionals.
As for the NullOpt, we provide the appropriate constructor and assignment operator, and that would be enough for writing myOpt = nopt, but that’s still not enough for myOpt = {}, since the latter would evaluate {} to a default-constructed object of type myOpt was templated on.
To fix this, we simply add another constructor:
template<typename T>
constexpr auto Opt<T>::operator = (std::initializer_list<std::nullptr_t>) -> Opt&
{
Destroy();
return *this;
}
This will lead to {} being evaluated as an empty initializer list and this operator overload being selected. In it, we just destroy the value as we wanted in the case of = {}.
For accessing the value we define appropriate * and -> operators. For my purposes, I don’t yet need the value() or value_or() methods, but they can be easily implemented. Since these operators do not check if the value is actually present, I just added an assert for debugging purposes:
template<typename T>
constexpr auto Opt<T>::operator * () & -> T&
{
assert(hasValue);
return value;
}
And of course, the bool() operator is as simple as:
template<typename T>
constexpr Opt<T>::operator bool() const
{
return hasValue;
}
Conclusion
In the end, we have a simplified but functional implementation of the optional. This could be expanded but for now I don’t have such a need. I hope to continue this series exploring various aspects of the standard library.