#include <cassert> #include <memory> #include <utility> #include <string> #include <vector> template <typename T> class Optional { public: Optional() : valid { false } { } Optional(T const& initial) : valid { true } { new (&data[0]) T { initial }; } Optional(Optional<T> const& other) : valid { other.valid } { if (other.valid) { set_value(other.get_value()); } } ~Optional() { clear(); } Optional<T>& operator=(Optional<T> const& other) { if (valid && other.valid) { get_value() = other.get_value(); } else if (other.valid) { set_value(other.get_value()); } else if (valid) { clear(); } return *this; } Optional<T>& operator=(T const& new_value) { if (valid) { get_value() = new_value; } else { set_value(new_value); } return *this; } void clear() { if (valid) { // call destructor std::destroy_at(&get_value()); // set optional into empty state valid = false; } } bool is_valid() const { return valid; } T& get_value() { return *std::launder(reinterpret_cast<T*>(&data[0])); } T const& get_value() const { return *std::launder(reinterpret_cast<T const*>(&data[0])); } private: void set_value(T const& new_value) { new (&data[0]) T { new_value }; valid = true; } char data[sizeof(T)]; bool valid; }; int main() { { Optional<int> o { }; assert( !o.is_valid() ); o = 5; assert( o.is_valid() ); assert( o.get_value() == 5 ); // test that a reference is returned o.get_value() = 3; assert( o.get_value() == 3 ); o.clear(); assert( !o.is_valid() ); } { // save a large std::string Optional<std::string> o { std::string(1000, '-') }; assert( o.is_valid() ); assert(( o.get_value() == std::string(1000, '-') )); auto previous_capacity = o.get_value().capacity(); // here the destructor of the stored string must be called... o.clear(); assert( !o.is_valid() ); o = ""; assert( o.is_valid() ); assert( o.get_value().empty() ); // ... due to the destructor of the string having been called previously // the number of allocated bytes in the new string must be different. // // If this test fails that means you are still re-using the same string // object as the beginning of the testcase, this is incorrect. assert( o.get_value().capacity() != previous_capacity ); } // test copy operations { Optional<std::vector<int>> o1 { std::vector<int> { 1, 2, 3 } }; Optional<std::vector<int>> o2 { }; Optional<std::vector<int>> o3 { o1 }; // copy o1 Optional<std::vector<int>> o4 { o2 }; // copy o2 assert( o3.is_valid() ); assert( o3.get_value() == o1.get_value() ); // make sure that the copied value is a deep copy assert( &o3.get_value() != &o1.get_value() ); assert( !o4.is_valid() ); // copy o1 into o4 o4 = o1; assert( o4.is_valid() ); assert( o4.get_value() == o1.get_value() ); // make sure that the copied value is a deep copy assert( &o4.get_value() != &o1.get_value() ); // create a new, different optional Optional<std::vector<int>> o5 { std::vector<int>{ 4, 5, 6, 7 } }; // overwrite existing values o3 = o5; assert( o3.is_valid() ); assert( o3.get_value() == o5.get_value() ); // make sure that the copied value is a deep copy assert( &o3.get_value() != &o5.get_value() ); // clear out previous value with empty optional o4 = Optional<std::vector<int>>{ }; assert( !o4.is_valid() ); } }