diff --git a/include/camp/camp.hpp b/include/camp/camp.hpp index b5f230b7..ad0b90f3 100644 --- a/include/camp/camp.hpp +++ b/include/camp/camp.hpp @@ -24,6 +24,7 @@ For details about use and distribution, please read LICENSE and NOTICE from #include "camp/size.hpp" #include "camp/tuple.hpp" #include "camp/value.hpp" +// #include "camp/optional.hpp" #include "camp/detail/test.hpp" diff --git a/include/camp/defines.hpp b/include/camp/defines.hpp index eb4f3263..7759a516 100644 --- a/include/camp/defines.hpp +++ b/include/camp/defines.hpp @@ -60,6 +60,10 @@ namespace camp #define CAMP_CONSTEXPR14 #endif +#if defined(__cpp_lib_is_swappable) && __cpp_lib_is_swappable >= 201603 +#define CAMP_HAS_SWAPPABLE_TRAITS +#endif + // define host device macros #define CAMP_HIP_HOST_DEVICE diff --git a/include/camp/optional.hpp b/include/camp/optional.hpp new file mode 100644 index 00000000..0869b591 --- /dev/null +++ b/include/camp/optional.hpp @@ -0,0 +1,372 @@ +/* +Copyright (c) 2016-21, Lawrence Livermore National Security, LLC. +Produced at the Lawrence Livermore National Laboratory +Maintained by Tom Scogland +CODE-756261, All rights reserved. +This file is part of camp. +For details about use and distribution, please read LICENSE and NOTICE from +http://github.com/llnl/camp +*/ + +#ifndef CAMP_OPTIONAL_HPP +#define CAMP_OPTIONAL_HPP + +#include +#include +#include +#include + +#include "camp/defines.hpp" + + +namespace camp +{ + +struct exception : std::exception +{ + exception() noexcept = default; + + exception(const exception& other) noexcept = default; + + exception& operator=(const exception& other) noexcept = default; + + virtual const char* what() const noexcept + { return m_what; } + + virtual ~exception() = default; + +protected: + const char* m_what = "camp::exception"; +}; + +struct bad_optional_access : camp::exception +{ + bad_optional_access() noexcept + : m_what("camp::bad_optional_access") + { } + + bad_optional_access( const bad_optional_access& other ) noexcept = default; + + bad_optional_access& operator=( const bad_optional_access& other ) noexcept = default; + + virtual const char* what() const noexcept + { return m_what; } + + virtual ~bad_optional_access() = default; +}; + + +struct nullopt_t { + explicit constexpr nullopt_t(int) {} +}; + +inline constexpr nullopt_t nullopt(-1); + + +template < typename T > +struct optional +{ + using value_type = T; + + constexpr optional() noexcept = default; + constexpr optional(camp::nullopt_t) noexcept : optional() { } + + constexpr optional(const optional& other) requires !std::is_copy_constructible::value = delete; + constexpr optional(const optional& other) requires std::is_trivially_copy_constructible::value = default; + + constexpr optional(const optional& other) requires std::is_copy_constructible::value && + !std::is_trivially_copy_constructible::value + { + if (other.has_value()) { + emplace(*other); + } + } + + constexpr optional(optional&& other) noexcept + requires std::is_trivially_move_constructible::value = default; + + constexpr optional(optional&& other) + noexcept(std::is_nothrow_move_constructible::value) + requires std::is_move_constructible::value && + !std::is_trivially_move_constructible::value + { + if (other.has_value()) { + emplace(std::move(*other)); + } + } + + + + + CAMP_HOST_DEVICE + optional& operator=(camp::nullopt_t) noexcept + { + reset(); + return *this; + } + + CAMP_CONSTEXPR14 typename std::enable_if::value + && std::is_copy_assignable::value), + optional&>::type + operator=(const optional& other) = delete; + + CAMP_CONSTEXPR14 typename std::enable_if<(std::is_trivially_copy_constructible::value + && std::is_trivially_copy_assignable::value + && std::is_trivially_destructible::value), + optional&>::type + operator=(const optional& other) = default; + + CAMP_HOST_DEVICE + CAMP_CONSTEXPR14 typename std::enable_if<(std::is_copy_constructible::value + && std::is_copy_assignable::value) + && !(std::is_trivially_copy_constructible::value + && std::is_trivially_copy_assignable::value + && std::is_trivially_destructible::value), + optional&>::type + operator=(const optional& other) + { + if (m_has_value && other.has_value()) { + **this = *other; + } else if (m_has_value) { + reset(); + } else if (other.has_value()) { + emplace(*other); + } + return *this; + } + + CAMP_CONSTEXPR14 typename std::enable_if<(std::is_move_constructible::value + && std::is_move_assignable::value) + && (std::is_trivially_move_constructible::value + && std::is_trivially_move_assignable::value + && std::is_trivially_destructible::value), + optional&>::type + operator=(optional&& other) = default; + + CAMP_HOST_DEVICE + CAMP_CONSTEXPR14 typename std::enable_if<(std::is_move_constructible::value + && std::is_move_assignable::value) + && !(std::is_trivially_move_constructible::value + && std::is_trivially_move_assignable::value + && std::is_trivially_destructible::value), + optional&>::type + operator=(optional&& other) + noexcept(std::is_nothrow_move_assignable::value + && std::is_nothrow_move_constructible::value) + { + if (m_has_value && other.has_value()) { + **this = std::move(*other); + } else if (m_has_value) { + reset(); + } else if (other.has_value()) { + emplace(std::move(*other)); + } + return *this; + } + + CAMP_HOST_DEVICE + template < typename U = T, + typename = typename std::enable_if< + !std::is_same< + typename std::remove_cv::type>::type, + camp::optional>::value && + std::is_constructible::value && + std::is_assignable::value && + (!std::is_scalar::value || + !std::is_same::type, T>::value)>::type > + optional& operator=(U&& value) + { + if (m_has_value) { + **this = std::move(std::forward(value)); + } else { + emplace(std::forward(value)); + } + return *this; + } + + CAMP_HOST_DEVICE + template < typename U, + typename = typename std::enable_if< + !(std::is_constructible&>::value && + std::is_constructible&>::value && + std::is_constructible&&>::value && + std::is_constructible&&>::value && + std::is_convertible&, T>::value && + std::is_convertible&, T>::value && + std::is_convertible&&, T>::value && + std::is_convertible&&, T>::value && + std::is_assignable&>::value && + std::is_assignable&>::value && + std::is_assignable&&>::value && + std::is_assignable&&>::value) && + std::is_constructible::value && + std::is_assignable::value >::type > + optional& operator=(const optional& other) + { + if (m_has_value && other.has_value()) { + **this = *other; + } else if (m_has_value) { + reset(); + } else if (other.has_value()) { + emplace(*other); + } + return *this; + } + + CAMP_HOST_DEVICE + template < typename U + typename = typename std::enable_if< + !(std::is_constructible&>::value && + std::is_constructible&>::value && + std::is_constructible&&>::value && + std::is_constructible&&>::value && + std::is_convertible&, T>::value && + std::is_convertible&, T>::value && + std::is_convertible&&, T>::value && + std::is_convertible&&, T>::value && + std::is_assignable&>::value && + std::is_assignable&>::value && + std::is_assignable&&>::value && + std::is_assignable&&>::value) && + std::is_constructible::value && + std::is_assignable::value >::type > + optional& operator=(optional&& other) + { + if (m_has_value && other.has_value()) { + **this = std::move(*other); + } else if (m_has_value) { + reset(); + } else if (other.has_value()) { + emplace(std::move(*other)); + } + return *this; + } + + // have to break this up with partial specialization + ~optional() requires std::is_trivially_destructible::value = default; + + CAMP_HOST_DEVICE + ~optional() requires !std::is_trivially_destructible::value + { + reset(); + } + + CAMP_HOST_DEVICE + constexpr explicit operator bool() const noexcept + { return m_has_value; } + CAMP_HOST_DEVICE + constexpr bool has_value() const noexcept + { return m_has_value; } + + CAMP_HOST_DEVICE + constexpr const T* operator->() const + { return reinterpret_cast(&m_storage); } + CAMP_HOST_DEVICE CAMP_CONSTEXPR14 + T* operator->() + { return reinterpret_cast< T*>(&m_storage); } + + CAMP_HOST_DEVICE + constexpr const T& operator*() const& + { return reinterpret_cast(m_storage); } + CAMP_HOST_DEVICE + CAMP_CONSTEXPR14 T& operator*() & + { return reinterpret_cast< T&>(m_storage); } + + CAMP_HOST_DEVICE + constexpr const T&& operator*() const&& + { return reinterpret_cast(m_storage); } + CAMP_HOST_DEVICE + CAMP_CONSTEXPR14 T&& operator*() && + { return reinterpret_cast< T&&>(m_storage); } + + constexpr const T& value() const& + { + return m_has_value ? reinterpret_cast(m_storage) + : throw camp::bad_optional_access(); + } + CAMP_CONSTEXPR14 T& value() & + { + return m_has_value ? reinterpret_cast< T&>(m_storage) + : throw camp::bad_optional_access(); + } + + constexpr const T&& value() const&& + { + return m_has_value ? reinterpret_cast(m_storage) + : throw camp::bad_optional_access(); + } + CAMP_CONSTEXPR14 T&& value() && + { + return m_has_value ? reinterpret_cast< T&&>(m_storage) + : throw camp::bad_optional_access(); + } + + CAMP_HOST_DEVICE + template < typename U > + constexpr T value_or(U&& default_value) const& + { + return m_has_value ? reinterpret_cast(m_storage) + : static_cast(std::forward(default_value)); + } + CAMP_HOST_DEVICE + template < typename U > + CAMP_CONSTEXPR14 T value_or(U&& default_value) && + { + return m_has_value ? reinterpret_cast< T&&>(m_storage) + : static_cast(std::forward(default_value)); + } + + void swap(optional& other) +#if defined(CAMP_HAS_SWAPPABLE_TRAITS) + noexcept(std::is_nothrow_move_constructible::value && + std::is_nothrow_swappable::value) +#endif + { + if (m_has_value && other.has_value()) { + using std::swap; + swap(**this, *other); + } else if (m_has_value) { + other.emplace(std::move(**this)); + reset(); + } else if (other.has_value()) { + emplace(std::move(*other)); + other.reset(); + } + } + + CAMP_HOST_DEVICE + void reset() noexcept + { + if (m_has_value) { + (**this).T::~T(); + m_has_value = false; + } + } + + CAMP_HOST_DEVICE + template < typename... Args > + T& emplace(Args&&... args) + { + reset(); + new(static_cast(&m_storage)) T(std::forward(args)...); + m_has_value = true; + return **this; + } + template < typename U, typename... Args, typename = std::enable_if< + std::is_constructible&, Args&&...>::value>::type > + T& emplace(std::initializer_list ilist, Args&&... args) + { + reset(); + new(static_cast(&m_storage)) T(ilist, std::forward(args)...); + m_has_value = true; + return **this; + } + +private: + typename std::aligned_storage::type m_storage; + bool m_has_value = false; +}; + +} // end namespace camp + +#endif /* CAMP_OPTIONAL_HPP */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a6ec3a54..666ee137 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -111,6 +111,7 @@ endfunction() camp_add_test(resource GTEST) camp_add_test(tuple GTEST) +camp_add_test(optional GTEST) camp_add_test(tuple_out_of_range RUN) diff --git a/test/optional.cpp b/test/optional.cpp new file mode 100644 index 00000000..7b1b4492 --- /dev/null +++ b/test/optional.cpp @@ -0,0 +1,21 @@ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +// Copyright (c) 2016-21, Lawrence Livermore National Security, LLC. +// +// Produced at the Lawrence Livermore National Laboratory +// +// LLNL-CODE-756261 +// +// All rights reserved. +// +// This file is part of CAMP. +// +// For details about use and distribution, please read CAMP/LICENSE. +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + +#include + +#include "camp/camp.hpp" +#include "gtest/gtest.h" + +// #include "camp/optional.hpp"