#include #include #include #include #include class EventLogger; class Event { public: enum EventType { DEFAULT_CONSTRUCT, COPY_CONSTRUCT, MOVE_CONSTRUCT, VALUE_CONSTRUCT, DESTRUCT, COPY_ASSIGN, MOVE_ASSIGN, VALUE_ASSIGN, REFERENCE, CONST_REFERENCE, RVALUE_REFERENCE, CONST_RVALUE_REFERENCE, SWAP, }; EventType Type; const EventLogger* Logger1; const EventLogger* Logger2; int Value; bool operator==(const Event& other) const; bool operator!=(const Event& other) const; }; bool Event::operator==(const Event& other) const { return this->Type == other.Type && this->Logger1 == other.Logger1 && this->Logger2 == other.Logger2 && this->Value == other.Value; } bool Event::operator!=(const Event& other) const { return !(*this == other); } static std::vector events; class EventLogger { public: EventLogger(); EventLogger(const EventLogger& other); EventLogger(EventLogger&& other); EventLogger(int value); ~EventLogger(); EventLogger& operator=(const EventLogger& other); EventLogger& operator=(EventLogger&& other); EventLogger& operator=(int value); void Reference() &; void Reference() const&; void Reference() &&; void Reference() const&&; int Value = 0; }; // Certain builds of GCC generate false -Wmaybe-uninitialized warnings when // doing a release build with the system version of std::optional. These // warnings do not manifest when using our own cm::optional implementation. // Silence these false warnings. #if defined(__GNUC__) && !defined(__clang__) # define BEGIN_IGNORE_UNINITIALIZED \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") # define END_IGNORE_UNINITIALIZED _Pragma("GCC diagnostic pop") #else # define BEGIN_IGNORE_UNINITIALIZED # define END_IGNORE_UNINITIALIZED #endif void swap(EventLogger& e1, EventLogger& e2) { BEGIN_IGNORE_UNINITIALIZED events.push_back({ Event::SWAP, &e1, &e2, e2.Value }); END_IGNORE_UNINITIALIZED auto tmp = e1.Value; e1.Value = e2.Value; e2.Value = tmp; } EventLogger::EventLogger() : Value(0) { events.push_back({ Event::DEFAULT_CONSTRUCT, this, nullptr, 0 }); } EventLogger::EventLogger(const EventLogger& other) : Value(other.Value) { events.push_back({ Event::COPY_CONSTRUCT, this, &other, other.Value }); } BEGIN_IGNORE_UNINITIALIZED EventLogger::EventLogger(EventLogger&& other) : Value(other.Value) { events.push_back({ Event::MOVE_CONSTRUCT, this, &other, other.Value }); } END_IGNORE_UNINITIALIZED EventLogger::EventLogger(int value) : Value(value) { events.push_back({ Event::VALUE_CONSTRUCT, this, nullptr, value }); } EventLogger::~EventLogger() { BEGIN_IGNORE_UNINITIALIZED events.push_back({ Event::DESTRUCT, this, nullptr, this->Value }); END_IGNORE_UNINITIALIZED } EventLogger& EventLogger::operator=(const EventLogger& other) { events.push_back({ Event::COPY_ASSIGN, this, &other, other.Value }); this->Value = other.Value; return *this; } EventLogger& EventLogger::operator=(EventLogger&& other) { events.push_back({ Event::MOVE_ASSIGN, this, &other, other.Value }); this->Value = other.Value; return *this; } EventLogger& EventLogger::operator=(int value) { events.push_back({ Event::VALUE_ASSIGN, this, nullptr, value }); this->Value = value; return *this; } void EventLogger::Reference() & { events.push_back({ Event::REFERENCE, this, nullptr, this->Value }); } void EventLogger::Reference() const& { events.push_back({ Event::CONST_REFERENCE, this, nullptr, this->Value }); } void EventLogger::Reference() && { events.push_back({ Event::RVALUE_REFERENCE, this, nullptr, this->Value }); } void EventLogger::Reference() const&& { events.push_back( { Event::CONST_RVALUE_REFERENCE, this, nullptr, this->Value }); } static bool testDefaultConstruct(std::vector& expected) { const cm::optional o{}; expected = {}; return true; } static bool testNulloptConstruct(std::vector& expected) { const cm::optional o{ cm::nullopt }; expected = {}; return true; } static bool testValueConstruct(std::vector& expected) { const cm::optional o{ 4 }; expected = { { Event::VALUE_CONSTRUCT, &*o, nullptr, 4 }, { Event::DESTRUCT, &*o, nullptr, 4 }, }; return true; } static bool testInPlaceConstruct(std::vector& expected) { const cm::optional o1{ cm::in_place, 4 }; const cm::optional o2{ cm::in_place_t{}, 4 }; expected = { { Event::VALUE_CONSTRUCT, &*o1, nullptr, 4 }, { Event::VALUE_CONSTRUCT, &*o2, nullptr, 4 }, { Event::DESTRUCT, &*o2, nullptr, 4 }, { Event::DESTRUCT, &*o1, nullptr, 4 }, }; return true; } static bool testCopyConstruct(std::vector& expected) { const cm::optional o1{ 4 }; const cm::optional o2{ o1 }; const cm::optional o3{}; const cm::optional o4{ o3 }; expected = { { Event::VALUE_CONSTRUCT, &*o1, nullptr, 4 }, { Event::COPY_CONSTRUCT, &*o2, &o1.value(), 4 }, { Event::DESTRUCT, &*o2, nullptr, 4 }, { Event::DESTRUCT, &*o1, nullptr, 4 }, }; return true; } static bool testMoveConstruct(std::vector& expected) { cm::optional o1{ 4 }; const cm::optional o2{ std::move(o1) }; cm::optional o3{}; const cm::optional o4{ std::move(o3) }; expected = { { Event::VALUE_CONSTRUCT, &*o1, nullptr, 4 }, { Event::MOVE_CONSTRUCT, &*o2, &*o1, 4 }, { Event::DESTRUCT, &*o2, nullptr, 4 }, { Event::DESTRUCT, &*o1, nullptr, 4 }, }; return true; } static bool testNulloptAssign(std::vector& expected) { cm::optional o1{ 4 }; auto const* v1 = &*o1; o1 = cm::nullopt; cm::optional o2{}; o2 = cm::nullopt; expected = { { Event::VALUE_CONSTRUCT, v1, nullptr, 4 }, { Event::DESTRUCT, v1, nullptr, 4 }, }; return true; } static bool testCopyAssign(std::vector& expected) { cm::optional o1{}; const cm::optional o2{ 4 }; auto const* v2 = &*o2; o1 = o2; auto const* v1 = &*o1; const cm::optional o3{ 5 }; auto const* v3 = &*o3; o1 = o3; const cm::optional o4{}; o1 = o4; o1 = o4; // Intentionally duplicated to test assigning an empty optional to // an empty optional expected = { { Event::VALUE_CONSTRUCT, v2, nullptr, 4 }, { Event::COPY_CONSTRUCT, v1, v2, 4 }, { Event::VALUE_CONSTRUCT, v3, nullptr, 5 }, { Event::COPY_ASSIGN, v1, v3, 5 }, { Event::DESTRUCT, v1, nullptr, 5 }, { Event::DESTRUCT, v3, nullptr, 5 }, { Event::DESTRUCT, v2, nullptr, 4 }, }; return true; } static bool testMoveAssign(std::vector& expected) { cm::optional o1{}; cm::optional o2{ 4 }; auto const* v2 = &*o2; o1 = std::move(o2); auto const* v1 = &*o1; cm::optional o3{ 5 }; auto const* v3 = &*o3; o1 = std::move(o3); cm::optional o4{}; o1 = std::move(o4); expected = { { Event::VALUE_CONSTRUCT, v2, nullptr, 4 }, { Event::MOVE_CONSTRUCT, v1, v2, 4 }, { Event::VALUE_CONSTRUCT, v3, nullptr, 5 }, { Event::MOVE_ASSIGN, v1, v3, 5 }, { Event::DESTRUCT, v1, nullptr, 5 }, { Event::DESTRUCT, v3, nullptr, 5 }, { Event::DESTRUCT, v2, nullptr, 4 }, }; return true; } static bool testPointer(std::vector& expected) { cm::optional o1{ 4 }; const cm::optional o2{ 5 }; o1->Reference(); o2->Reference(); expected = { { Event::VALUE_CONSTRUCT, &*o1, nullptr, 4 }, { Event::VALUE_CONSTRUCT, &*o2, nullptr, 5 }, { Event::REFERENCE, &*o1, nullptr, 4 }, { Event::CONST_REFERENCE, &*o2, nullptr, 5 }, { Event::DESTRUCT, &*o2, nullptr, 5 }, { Event::DESTRUCT, &*o1, nullptr, 4 }, }; return true; } #if !__GNUC__ || __GNUC__ > 4 # define ALLOW_CONST_RVALUE #endif static bool testDereference(std::vector& expected) { cm::optional o1{ 4 }; auto const* v1 = &*o1; const cm::optional o2{ 5 }; auto const* v2 = &*o2; (*o1).Reference(); (*o2).Reference(); (*std::move(o1)).Reference(); #ifdef ALLOW_CONST_RVALUE (*std::move(o2)).Reference(); // Broken in GCC 4.9.0. Sigh... #endif expected = { { Event::VALUE_CONSTRUCT, v1, nullptr, 4 }, { Event::VALUE_CONSTRUCT, v2, nullptr, 5 }, { Event::REFERENCE, v1, nullptr, 4 }, { Event::CONST_REFERENCE, v2, nullptr, 5 }, { Event::RVALUE_REFERENCE, v1, nullptr, 4 }, #ifdef ALLOW_CONST_RVALUE { Event::CONST_RVALUE_REFERENCE, v2, nullptr, 5 }, #endif { Event::DESTRUCT, v2, nullptr, 5 }, { Event::DESTRUCT, v1, nullptr, 4 }, }; return true; } static bool testHasValue(std::vector& expected) { bool retval = true; const cm::optional o1{ 4 }; const cm::optional o2{}; if (!o1.has_value()) { std::cout << "o1 should have a value" << std::endl; retval = false; } if (!o1) { std::cout << "(bool)o1 should be true" << std::endl; retval = false; } if (o2.has_value()) { std::cout << "o2 should not have a value" << std::endl; retval = false; } if (o2) { std::cout << "(bool)o2 should be false" << std::endl; retval = false; } expected = { { Event::VALUE_CONSTRUCT, &*o1, nullptr, 4 }, { Event::DESTRUCT, &*o1, nullptr, 4 }, }; return retval; } static bool testValue(std::vector& expected) { bool retval = true; cm::optional o1{ 4 }; const cm::optional o2{ 5 }; cm::optional o3{}; const cm::optional o4{}; o1.value().Reference(); o2.value().Reference(); bool thrown = false; try { (void)o3.value(); } catch (cm::bad_optional_access&) { thrown = true; } if (!thrown) { std::cout << "o3.value() did not throw" << std::endl; retval = false; } thrown = false; try { (void)o4.value(); } catch (cm::bad_optional_access&) { thrown = true; } if (!thrown) { std::cout << "o4.value() did not throw" << std::endl; retval = false; } expected = { { Event::VALUE_CONSTRUCT, &*o1, nullptr, 4 }, { Event::VALUE_CONSTRUCT, &*o2, nullptr, 5 }, { Event::REFERENCE, &*o1, nullptr, 4 }, { Event::CONST_REFERENCE, &*o2, nullptr, 5 }, { Event::DESTRUCT, &*o2, nullptr, 5 }, { Event::DESTRUCT, &*o1, nullptr, 4 }, }; return retval; } static bool testValueOr() { bool retval = true; const cm::optional o1{ 4 }; cm::optional o2{ 5 }; const cm::optional o3{}; cm::optional o4{}; EventLogger e1{ 6 }; EventLogger e2{ 7 }; EventLogger e3{ 8 }; EventLogger e4{ 9 }; EventLogger r1 = o1.value_or(e1); if (r1.Value != 4) { std::cout << "r1.Value should be 4" << std::endl; retval = false; } EventLogger r2 = std::move(o2).value_or(e2); if (r2.Value != 5) { std::cout << "r2.Value should be 5" << std::endl; retval = false; } EventLogger r3 = o3.value_or(e3); if (r3.Value != 8) { std::cout << "r3.Value should be 8" << std::endl; retval = false; } EventLogger r4 = std::move(o4).value_or(e4); if (r4.Value != 9) { std::cout << "r4.Value should be 9" << std::endl; retval = false; } return retval; } static bool testSwap(std::vector& expected) { bool retval = true; cm::optional o1{ 4 }; auto const* v1 = &*o1; cm::optional o2{}; o1.swap(o2); auto const* v2 = &*o2; if (o1.has_value()) { std::cout << "o1 should not have value" << std::endl; retval = false; } if (!o2.has_value()) { std::cout << "o2 should have value" << std::endl; retval = false; } if (o2.value().Value != 4) { std::cout << "value of o2 should be 4" << std::endl; retval = false; } o1.swap(o2); if (!o1.has_value()) { std::cout << "o1 should have value" << std::endl; retval = false; } if (o1.value().Value != 4) { std::cout << "value of o1 should be 4" << std::endl; retval = false; } if (o2.has_value()) { std::cout << "o2 should not have value" << std::endl; retval = false; } o2.emplace(5); o1.swap(o2); if (!o1.has_value()) { std::cout << "o1 should have value" << std::endl; retval = false; } if (o1.value().Value != 5) { std::cout << "value of o1 should be 5" << std::endl; retval = false; } if (!o2.has_value()) { std::cout << "o2 should not have value" << std::endl; retval = false; } if (o2.value().Value != 4) { std::cout << "value of o2 should be 4" << std::endl; retval = false; } o1.reset(); o2.reset(); o1.swap(o2); if (o1.has_value()) { std::cout << "o1 should not have value" << std::endl; retval = false; } if (o2.has_value()) { std::cout << "o2 should not have value" << std::endl; retval = false; } expected = { { Event::VALUE_CONSTRUCT, v1, nullptr, 4 }, { Event::MOVE_CONSTRUCT, v2, v1, 4 }, { Event::DESTRUCT, v1, nullptr, 4 }, { Event::MOVE_CONSTRUCT, v1, v2, 4 }, { Event::DESTRUCT, v2, nullptr, 4 }, { Event::VALUE_CONSTRUCT, v2, nullptr, 5 }, { Event::SWAP, v1, v2, 5 }, { Event::DESTRUCT, v1, nullptr, 5 }, { Event::DESTRUCT, v2, nullptr, 4 }, }; return retval; } static bool testReset(std::vector& expected) { bool retval = true; cm::optional o{ 4 }; auto const* v = &*o; o.reset(); if (o.has_value()) { std::cout << "o should not have value" << std::endl; retval = false; } o.reset(); expected = { { Event::VALUE_CONSTRUCT, v, nullptr, 4 }, { Event::DESTRUCT, v, nullptr, 4 }, }; return retval; } static bool testEmplace(std::vector& expected) { cm::optional o{ 4 }; o.emplace(5); o.reset(); o.emplace(); expected = { { Event::VALUE_CONSTRUCT, &*o, nullptr, 4 }, { Event::DESTRUCT, &*o, nullptr, 4 }, { Event::VALUE_CONSTRUCT, &*o, nullptr, 5 }, { Event::DESTRUCT, &*o, nullptr, 5 }, { Event::DEFAULT_CONSTRUCT, &*o, nullptr, 0 }, { Event::DESTRUCT, &*o, nullptr, 0 }, }; return true; } static bool testMakeOptional(std::vector& expected) { EventLogger e{ 4 }; cm::optional o1 = cm::make_optional(e); cm::optional o2 = cm::make_optional(5); expected = { { Event::VALUE_CONSTRUCT, &e, nullptr, 4 }, { Event::COPY_CONSTRUCT, &*o1, &e, 4 }, { Event::VALUE_CONSTRUCT, &*o2, nullptr, 5 }, { Event::DESTRUCT, &*o2, nullptr, 5 }, { Event::DESTRUCT, &*o1, nullptr, 4 }, { Event::DESTRUCT, &e, nullptr, 4 }, }; return true; } static bool testMemoryRange(std::vector& expected) { bool retval = true; cm::optional o{ 4 }; auto* ostart = &o; auto* oend = ostart + 1; auto* estart = &o.value(); auto* eend = estart + 1; if (static_cast(estart) < static_cast(ostart) || static_cast(eend) > static_cast(oend)) { std::cout << "value is not within memory range of optional" << std::endl; retval = false; } expected = { { Event::VALUE_CONSTRUCT, &*o, nullptr, 4 }, { Event::DESTRUCT, &*o, nullptr, 4 }, }; return retval; } int testOptional(int /*unused*/, char* /*unused*/ []) { int retval = 0; #define DO_EVENT_TEST(name) \ do { \ events.clear(); \ std::vector expected; \ if (!name(expected)) { \ std::cout << "in " #name << std::endl; \ retval = 1; \ } else if (expected != events) { \ std::cout << #name " did not produce expected events" << std::endl; \ retval = 1; \ } \ } while (0) #define DO_TEST(name) \ do { \ if (!name()) { \ std::cout << "in " #name << std::endl; \ retval = 1; \ } \ } while (0) DO_EVENT_TEST(testDefaultConstruct); DO_EVENT_TEST(testNulloptConstruct); DO_EVENT_TEST(testValueConstruct); DO_EVENT_TEST(testInPlaceConstruct); DO_EVENT_TEST(testCopyConstruct); DO_EVENT_TEST(testMoveConstruct); DO_EVENT_TEST(testNulloptAssign); DO_EVENT_TEST(testCopyAssign); DO_EVENT_TEST(testMoveAssign); DO_EVENT_TEST(testPointer); DO_EVENT_TEST(testDereference); DO_EVENT_TEST(testHasValue); DO_EVENT_TEST(testValue); DO_TEST(testValueOr); DO_EVENT_TEST(testSwap); DO_EVENT_TEST(testReset); DO_EVENT_TEST(testEmplace); DO_EVENT_TEST(testMakeOptional); DO_EVENT_TEST(testMemoryRange); return retval; }