#include #include #include #include #include template class Optional { public: Optional() : empty { true }, valid { false } { } Optional(T const& initial) : value { initial }, valid { true } { } Optional(Optional const& other) : valid { other.valid } { if (other.valid) { set_value(other.get_value()); } } ~Optional() { clear(); } Optional& operator=(Optional 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& 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; empty = true; } } bool is_valid() const { return valid; } T& get_value() { return value; } T const& get_value() const { return value; } private: void set_value(T const& new_value) { new (&value) T { new_value }; valid = true; } union { T value; bool empty; }; bool valid; }; int main() { { Optional 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 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> o1 { std::vector { 1, 2, 3 } }; Optional> o2 { }; Optional> o3 { o1 }; // copy o1 Optional> 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> o5 { std::vector{ 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>{ }; assert( !o4.is_valid() ); } }