Commit 197c5e12 authored by Kyle Edwards's avatar Kyle Edwards Committed by Brad King
Browse files

Source: Add cm::optional

parent 170fcd71
/* 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
/* 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
......@@ -9,6 +9,7 @@ set(CMakeLib_TESTS
testGeneratedFileStream.cxx
testRST.cxx
testRange.cxx
testOptional.cxx
testString.cxx
testStringAlgorithms.cxx
testSystemTools.cxx
......
#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 },