Browse Source

Merge topic 'optional'

197c5e12ad Source: Add cm::optional
170fcd715f Extend C++17 feature checks to require std::optional

Acked-by: Kitware Robot <[email protected]>
Acked-by: Sebastian Holtermann <[email protected]>
Merge-request: !3644
Brad King 6 years ago
parent
commit
f5a7ae0c4b

+ 4 - 1
Source/Checks/cm_cxx17_check.cpp

@@ -1,6 +1,7 @@
 #include <cstdio>
 #include <iterator>
 #include <memory>
+#include <optional>
 #include <unordered_map>
 
 #ifdef _MSC_VER
@@ -27,5 +28,7 @@ int main()
   IDispatchPtr disp(ptr);
 #endif
 
-  return *u + *ai + *(bi - 1) + (3 - static_cast<int>(ci));
+  std::optional<int> oi = 0;
+
+  return *u + *ai + *(bi - 1) + (3 - static_cast<int>(ci)) + oi.value();
 }

+ 343 - 0
Source/cm_optional.hxx

@@ -0,0 +1,343 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cm_optional_hxx
+#define cm_optional_hxx
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#  define CMake_HAVE_CXX_OPTIONAL
+#endif
+
+#if defined(CMake_HAVE_CXX_OPTIONAL)
+#  include <optional>
+#else
+#  include "cm_utility.hxx"
+#  include <memory>
+#  include <utility>
+#endif
+
+namespace cm {
+
+#if defined(CMake_HAVE_CXX_OPTIONAL)
+
+using std::nullopt_t;
+using std::nullopt;
+using std::optional;
+using std::bad_optional_access;
+using std::make_optional;
+
+#else
+
+class bad_optional_access : public std::exception
+{
+  using std::exception::exception;
+};
+
+struct nullopt_t
+{
+  explicit constexpr nullopt_t(int) {}
+};
+
+constexpr nullopt_t nullopt{ 0 };
+
+template <typename T>
+class optional
+{
+public:
+  using value_type = T;
+
+  optional() noexcept = default;
+  optional(nullopt_t) noexcept;
+  optional(const optional& other);
+  optional(optional&& other) noexcept;
+
+  template <typename... Args>
+  explicit optional(cm::in_place_t, Args&&... args);
+
+  template <
+    typename U = T,
+    typename = typename std::enable_if<
+      std::is_constructible<T, U&&>::value &&
+      !std::is_same<typename std::decay<U>::type, cm::in_place_t>::value &&
+      !std::is_same<typename std::decay<U>::type,
+                    cm::optional<T>>::value>::type>
+  optional(U&& v);
+
+  ~optional();
+
+  optional& operator=(nullopt_t) noexcept;
+  optional& operator=(const optional& other);
+  optional& operator=(optional&& other) noexcept;
+
+  template <
+    typename U = T,
+    typename = typename std::enable_if<
+      !std::is_same<typename std::decay<U>::type, cm::optional<T>>::value &&
+      std::is_constructible<T, U>::value && std::is_assignable<T&, U>::value &&
+      (!std::is_scalar<T>::value ||
+       !std::is_same<typename std::decay<U>::type, T>::value)>::type>
+  optional& operator=(U&& v);
+
+  const T* operator->() const;
+  T* operator->();
+  const T& operator*() const&;
+  T& operator*() &;
+  const T&& operator*() const&&;
+  T&& operator*() &&;
+
+  explicit operator bool() const noexcept;
+  bool has_value() const noexcept;
+
+  T& value() &;
+  const T& value() const&;
+
+  T&& value() &&;
+  const T&& value() const&&;
+
+  template <typename U>
+  T value_or(U&& default_value) const&;
+
+  template <typename U>
+  T value_or(U&& default_value) &&;
+
+  void swap(optional& other) noexcept;
+  void reset() noexcept;
+
+  template <typename... Args>
+  T& emplace(Args&&... args);
+
+private:
+  bool _has_value = false;
+  std::allocator<T> _allocator;
+  union _mem_union
+  {
+    T value;
+
+    // Explicit constructor and destructor is required to make this work
+    _mem_union() noexcept {}
+    ~_mem_union() noexcept {}
+  } _mem;
+};
+
+template <typename T>
+optional<typename std::decay<T>::type> make_optional(T&& value)
+{
+  return optional<typename std::decay<T>::type>(std::forward<T>(value));
+}
+
+template <typename T, class... Args>
+optional<T> make_optional(Args&&... args)
+{
+  return optional<T>(in_place, std::forward<Args>(args)...);
+}
+
+template <typename T>
+optional<T>::optional(nullopt_t) noexcept
+{
+}
+
+template <typename T>
+optional<T>::optional(const optional& other)
+{
+  *this = other;
+}
+
+template <typename T>
+optional<T>::optional(optional&& other) noexcept
+{
+  *this = std::move(other);
+}
+
+template <typename T>
+template <typename... Args>
+optional<T>::optional(cm::in_place_t, Args&&... args)
+{
+  this->emplace(std::forward<Args>(args)...);
+}
+
+template <typename T>
+template <typename U, typename>
+optional<T>::optional(U&& v)
+{
+  this->emplace(std::forward<U>(v));
+}
+
+template <typename T>
+optional<T>::~optional()
+{
+  this->reset();
+}
+
+template <typename T>
+optional<T>& optional<T>::operator=(nullopt_t) noexcept
+{
+  this->reset();
+  return *this;
+}
+
+template <typename T>
+optional<T>& optional<T>::operator=(const optional& other)
+{
+  if (other.has_value()) {
+    if (this->has_value()) {
+      this->value() = *other;
+    } else {
+      this->emplace(*other);
+    }
+  } else {
+    this->reset();
+  }
+  return *this;
+}
+
+template <typename T>
+optional<T>& optional<T>::operator=(optional&& other) noexcept
+{
+  if (other.has_value()) {
+    if (this->has_value()) {
+      this->value() = std::move(*other);
+    } else {
+      this->emplace(std::move(*other));
+    }
+  } else {
+    this->reset();
+  }
+  return *this;
+}
+
+template <typename T>
+template <typename U, typename>
+optional<T>& optional<T>::operator=(U&& v)
+{
+  if (this->has_value()) {
+    this->value() = v;
+  } else {
+    this->emplace(std::forward<U>(v));
+  }
+  return *this;
+}
+
+template <typename T>
+const T* optional<T>::operator->() const
+{
+  return &**this;
+}
+
+template <typename T>
+T* optional<T>::operator->()
+{
+  return &**this;
+}
+
+template <typename T>
+const T& optional<T>::operator*() const&
+{
+  return this->_mem.value;
+}
+
+template <typename T>
+T& optional<T>::operator*() &
+{
+  return this->_mem.value;
+}
+
+template <typename T>
+const T&& optional<T>::operator*() const&&
+{
+  return std::move(**this);
+}
+
+template <typename T>
+T&& optional<T>::operator*() &&
+{
+  return std::move(**this);
+}
+
+template <typename T>
+bool optional<T>::has_value() const noexcept
+{
+  return this->_has_value;
+}
+
+template <typename T>
+optional<T>::operator bool() const noexcept
+{
+  return this->has_value();
+}
+
+template <typename T>
+T& optional<T>::value() &
+{
+  if (!this->has_value()) {
+    throw cm::bad_optional_access{};
+  }
+  return **this;
+}
+
+template <typename T>
+const T& optional<T>::value() const&
+{
+  if (!this->has_value()) {
+    throw cm::bad_optional_access{};
+  }
+  return **this;
+}
+
+template <typename T>
+template <typename U>
+T optional<T>::value_or(U&& default_value) const&
+{
+  return bool(*this) ? **this : static_cast<T>(std::forward<U>(default_value));
+}
+
+template <typename T>
+template <typename U>
+T optional<T>::value_or(U&& default_value) &&
+{
+  return bool(*this) ? std::move(**this)
+                     : static_cast<T>(std::forward<U>(default_value));
+}
+
+template <typename T>
+void optional<T>::swap(optional& other) noexcept
+{
+  if (this->has_value()) {
+    if (other.has_value()) {
+      using std::swap;
+      swap(**this, *other);
+    } else {
+      other.emplace(std::move(**this));
+      this->reset();
+    }
+  } else if (other.has_value()) {
+    this->emplace(std::move(*other));
+    other.reset();
+  }
+}
+
+template <typename T>
+void optional<T>::reset() noexcept
+{
+  if (this->has_value()) {
+    this->_has_value = false;
+    std::allocator_traits<std::allocator<T>>::destroy(this->_allocator,
+                                                      &**this);
+  }
+}
+
+template <typename T>
+template <typename... Args>
+T& optional<T>::emplace(Args&&... args)
+{
+  this->reset();
+  std::allocator_traits<std::allocator<T>>::construct(
+    this->_allocator, &**this, std::forward<Args>(args)...);
+  this->_has_value = true;
+  return this->value();
+}
+
+#endif
+}
+
+#endif

+ 35 - 0
Source/cm_utility.hxx

@@ -0,0 +1,35 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cm_utility_hxx
+#define cm_utility_hxx
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#  define CMake_HAVE_CXX_IN_PLACE
+#endif
+
+#if defined(CMake_HAVE_CXX_IN_PLACE)
+#  include <utility>
+#endif
+
+namespace cm {
+
+#if defined(CMake_HAVE_CXX_IN_PLACE)
+
+using std::in_place_t;
+using std::in_place;
+
+#else
+
+struct in_place_t
+{
+  explicit in_place_t() = default;
+};
+
+constexpr in_place_t in_place{};
+
+#endif
+}
+
+#endif

+ 1 - 0
Tests/CMakeLib/CMakeLists.txt

@@ -9,6 +9,7 @@ set(CMakeLib_TESTS
   testGeneratedFileStream.cxx
   testRST.cxx
   testRange.cxx
+  testOptional.cxx
   testString.cxx
   testStringAlgorithms.cxx
   testSystemTools.cxx

+ 690 - 0
Tests/CMakeLib/testOptional.cxx

@@ -0,0 +1,690 @@
+#include "cm_optional.hxx"
+#include "cm_utility.hxx"
+
+#include <iostream>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+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<Event> 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<Event>& expected)
+{
+  const cm::optional<EventLogger> o{};
+
+  expected = {};
+  return true;
+}
+
+static bool testNulloptConstruct(std::vector<Event>& expected)
+{
+  const cm::optional<EventLogger> o{ cm::nullopt };
+
+  expected = {};
+  return true;
+}
+
+static bool testValueConstruct(std::vector<Event>& expected)
+{
+  const cm::optional<EventLogger> o{ 4 };
+
+  expected = {
+    { Event::VALUE_CONSTRUCT, &*o, nullptr, 4 },
+    { Event::DESTRUCT, &*o, nullptr, 4 },
+  };
+  return true;
+}
+
+static bool testInPlaceConstruct(std::vector<Event>& expected)
+{
+  const cm::optional<EventLogger> o1{ cm::in_place, 4 };
+  const cm::optional<EventLogger> 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<Event>& expected)
+{
+  const cm::optional<EventLogger> o1{ 4 };
+  const cm::optional<EventLogger> o2{ o1 };
+  const cm::optional<EventLogger> o3{};
+  const cm::optional<EventLogger> 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<Event>& expected)
+{
+  cm::optional<EventLogger> o1{ 4 };
+  const cm::optional<EventLogger> o2{ std::move(o1) };
+  cm::optional<EventLogger> o3{};
+  const cm::optional<EventLogger> o4{ std::move(o3) };
+
+  expected = {
+    { Event::VALUE_CONSTRUCT, &*o1, nullptr, 4 },
+    { Event::MOVE_CONSTRUCT, &*o2, &o1.value(), 4 },
+    { Event::DESTRUCT, &*o2, nullptr, 4 },
+    { Event::DESTRUCT, &*o1, nullptr, 4 },
+  };
+  return true;
+}
+
+static bool testNulloptAssign(std::vector<Event>& expected)
+{
+  cm::optional<EventLogger> o1{ 4 };
+  o1 = cm::nullopt;
+  cm::optional<EventLogger> o2{};
+  o2 = cm::nullopt;
+
+  expected = {
+    { Event::VALUE_CONSTRUCT, &*o1, nullptr, 4 },
+    { Event::DESTRUCT, &*o1, nullptr, 4 },
+  };
+  return true;
+}
+
+static bool testCopyAssign(std::vector<Event>& expected)
+{
+  cm::optional<EventLogger> o1{};
+  const cm::optional<EventLogger> o2{ 4 };
+  o1 = o2;
+  const cm::optional<EventLogger> o3{ 5 };
+  o1 = o3;
+  const cm::optional<EventLogger> o4{};
+  o1 = o4;
+  o1 = o4; // Intentionally duplicated to test assigning an empty optional to
+  // an empty optional
+
+  expected = {
+    { Event::VALUE_CONSTRUCT, &*o2, nullptr, 4 },
+    { Event::COPY_CONSTRUCT, &*o1, &*o2, 4 },
+    { Event::VALUE_CONSTRUCT, &*o3, nullptr, 5 },
+    { Event::COPY_ASSIGN, &*o1, &*o3, 5 },
+    { Event::DESTRUCT, &*o1, nullptr, 5 },
+    { Event::DESTRUCT, &o3.value(), nullptr, 5 },
+    { Event::DESTRUCT, &o2.value(), nullptr, 4 },
+  };
+  return true;
+}
+
+static bool testMoveAssign(std::vector<Event>& expected)
+{
+  cm::optional<EventLogger> o1{};
+  cm::optional<EventLogger> o2{ 4 };
+  o1 = std::move(o2);
+  cm::optional<EventLogger> o3{ 5 };
+  o1 = std::move(o3);
+  cm::optional<EventLogger> o4{};
+  o1 = std::move(o4);
+
+  expected = {
+    { Event::VALUE_CONSTRUCT, &*o2, nullptr, 4 },
+    { Event::MOVE_CONSTRUCT, &*o1, &*o2, 4 },
+    { Event::VALUE_CONSTRUCT, &*o3, nullptr, 5 },
+    { Event::MOVE_ASSIGN, &*o1, &*o3, 5 },
+    { Event::DESTRUCT, &*o1, nullptr, 5 },
+    { Event::DESTRUCT, &*o3, nullptr, 5 },
+    { Event::DESTRUCT, &*o2, nullptr, 4 },
+  };
+  return true;
+}
+
+static bool testPointer(std::vector<Event>& expected)
+{
+  cm::optional<EventLogger> o1{ 4 };
+  const cm::optional<EventLogger> 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<Event>& expected)
+{
+  cm::optional<EventLogger> o1{ 4 };
+  const cm::optional<EventLogger> o2{ 5 };
+
+  (*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, &*o1, nullptr, 4 },
+    { Event::VALUE_CONSTRUCT, &*o2, nullptr, 5 },
+    { Event::REFERENCE, &*o1, nullptr, 4 },
+    { Event::CONST_REFERENCE, &*o2, nullptr, 5 },
+    { Event::RVALUE_REFERENCE, &*o1, nullptr, 4 },
+#ifdef ALLOW_CONST_RVALUE
+    { Event::CONST_RVALUE_REFERENCE, &*o2, nullptr, 5 },
+#endif
+    { Event::DESTRUCT, &*o2, nullptr, 5 },
+    { Event::DESTRUCT, &*o1, nullptr, 4 },
+  };
+  return true;
+}
+
+static bool testHasValue(std::vector<Event>& expected)
+{
+  bool retval = true;
+
+  const cm::optional<EventLogger> o1{ 4 };
+  const cm::optional<EventLogger> 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<Event>& expected)
+{
+  bool retval = true;
+
+  cm::optional<EventLogger> o1{ 4 };
+  const cm::optional<EventLogger> o2{ 5 };
+  cm::optional<EventLogger> o3{};
+  const cm::optional<EventLogger> 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<EventLogger> o1{ 4 };
+  cm::optional<EventLogger> o2{ 5 };
+  const cm::optional<EventLogger> o3{};
+  cm::optional<EventLogger> 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<Event>& expected)
+{
+  bool retval = true;
+
+  cm::optional<EventLogger> o1{ 4 };
+  cm::optional<EventLogger> o2{};
+
+  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 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, &*o1, nullptr, 4 },
+    { Event::MOVE_CONSTRUCT, &*o2, &*o1, 4 },
+    { Event::DESTRUCT, &*o1, nullptr, 4 },
+    { Event::MOVE_CONSTRUCT, &*o1, &*o2, 4 },
+    { Event::DESTRUCT, &*o2, nullptr, 4 },
+    { Event::VALUE_CONSTRUCT, &*o2, nullptr, 5 },
+    { Event::SWAP, &*o1, &*o2, 5 },
+    { Event::DESTRUCT, &*o1, nullptr, 5 },
+    { Event::DESTRUCT, &*o2, nullptr, 4 },
+  };
+  return retval;
+}
+
+static bool testReset(std::vector<Event>& expected)
+{
+  bool retval = true;
+
+  cm::optional<EventLogger> o{ 4 };
+
+  o.reset();
+
+  if (o.has_value()) {
+    std::cout << "o should not have value" << std::endl;
+    retval = false;
+  }
+
+  o.reset();
+
+  expected = {
+    { Event::VALUE_CONSTRUCT, &*o, nullptr, 4 },
+    { Event::DESTRUCT, &*o, nullptr, 4 },
+  };
+  return retval;
+}
+
+static bool testEmplace(std::vector<Event>& expected)
+{
+  cm::optional<EventLogger> 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<Event>& expected)
+{
+  EventLogger e{ 4 };
+  cm::optional<EventLogger> o1 = cm::make_optional<EventLogger>(e);
+  cm::optional<EventLogger> o2 = cm::make_optional<EventLogger>(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<Event>& expected)
+{
+  bool retval = true;
+
+  cm::optional<EventLogger> o{ 4 };
+
+  auto* ostart = &o;
+  auto* oend = ostart + 1;
+  auto* estart = &o.value();
+  auto* eend = estart + 1;
+
+  if (static_cast<void*>(estart) < static_cast<void*>(ostart) ||
+      static_cast<void*>(eend) > static_cast<void*>(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<Event> 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;
+}

+ 15 - 1
bootstrap

@@ -1147,6 +1147,20 @@ int check_cxx14()
 }
 #endif
 
+#if __cplusplus >= 201703L
+#include <optional>
+int check_cxx17()
+{
+  std::optional<int> oi = 0;
+  return oi.value();
+}
+#else
+int check_cxx17()
+{
+  return 0;
+}
+#endif
+
 class Class
 {
 public:
@@ -1157,7 +1171,7 @@ private:
 int main()
 {
   auto const c = std::unique_ptr<Class>(new Class);
-  std::cout << c->Get() << check_cxx14() << std::endl;
+  std::cout << c->Get() << check_cxx14() << check_cxx17() << std::endl;
   return 0;
 }
 ' > "${TMPFILE}.cxx"