Sendit support optional<T&> and Result<T&, E>

This commit is contained in:
Jeremy Rifkin 2025-01-26 19:18:47 -06:00
parent 293f4d1593
commit c9826616b5
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4
4 changed files with 159 additions and 22 deletions

View File

@ -1,6 +1,7 @@
#ifndef OPTIONAL_HPP
#define OPTIONAL_HPP
#include <functional>
#include <new>
#include <type_traits>
#include <utility>
@ -14,14 +15,27 @@ namespace detail {
static constexpr nullopt_t nullopt;
template<
typename T,
typename std::enable_if<
!std::is_same<typename std::decay<T>::type, void>::value && !std::is_rvalue_reference<T>::value,
int
>::type = 0
>
using well_behaved = std::conditional_t<
std::is_reference<T>::value, std::reference_wrapper<std::remove_reference_t<T>>, T
>;
template<
typename T,
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
>
class optional {
using value_type = well_behaved<T>;
union {
char x;
T uvalue;
value_type uvalue;
};
bool holds_value = false;
@ -37,16 +51,16 @@ namespace detail {
optional(const optional& other) : holds_value(other.holds_value) {
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(other.uvalue);
new (static_cast<void*>(std::addressof(uvalue))) value_type(other.uvalue);
}
}
optional(optional&& other)
noexcept(std::is_nothrow_move_constructible<T>::value)
noexcept(std::is_nothrow_move_constructible<value_type>::value)
: holds_value(other.holds_value)
{
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
}
}
@ -57,11 +71,11 @@ namespace detail {
}
optional& operator=(optional&& other)
noexcept(std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value)
noexcept(std::is_nothrow_move_assignable<value_type>::value && std::is_nothrow_move_constructible<value_type>::value)
{
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
holds_value = true;
}
return *this;
@ -72,7 +86,7 @@ namespace detail {
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::forward<U>(value));
}
template<
@ -94,11 +108,11 @@ namespace detail {
if(holds_value && other.holds_value) {
std::swap(uvalue, other.uvalue);
} else if(holds_value && !other.holds_value) {
new (&other.uvalue) T(std::move(uvalue));
uvalue.~T();
new (&other.uvalue) value_type(std::move(uvalue));
uvalue.~value_type();
} else if(!holds_value && other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
other.uvalue.~T();
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
other.uvalue.~value_type();
}
std::swap(holds_value, other.holds_value);
}
@ -113,7 +127,7 @@ namespace detail {
void reset() {
if(holds_value) {
uvalue.~T();
uvalue.~value_type();
}
holds_value = false;
}
@ -140,12 +154,12 @@ namespace detail {
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
return holds_value ? static_cast<T>(uvalue) : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(default_value));
return holds_value ? static_cast<T>(std::move(uvalue)) : static_cast<T>(std::forward<U>(default_value));
}
};
}

View File

@ -13,20 +13,21 @@ namespace cpptrace {
namespace detail {
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
class Result {
using value_type = well_behaved<T>;
union {
T value_;
value_type value_;
E error_;
};
enum class member { value, error };
member active;
public:
Result(T&& value) : value_(std::move(value)), active(member::value) {}
Result(value_type&& value) : value_(std::move(value)), active(member::value) {}
Result(E&& error) : error_(std::move(error)), active(member::error) {
if(!should_absorb_trace_exceptions()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(T& value) : value_(T(value)), active(member::value) {}
Result(value_type& value) : value_(value_type(value)), active(member::value) {}
Result(E& error) : error_(E(error)), active(member::error) {
if(!should_absorb_trace_exceptions()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
@ -34,14 +35,14 @@ namespace detail {
}
Result(Result&& other) : active(other.active) {
if(other.active == member::value) {
new (&value_) T(std::move(other.value_));
new (&value_) value_type(std::move(other.value_));
} else {
new (&error_) E(std::move(other.error_));
}
}
~Result() {
if(active == member::value) {
value_.~T();
value_.~value_type();
} else {
error_.~E();
}
@ -107,12 +108,12 @@ namespace detail {
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
return has_value() ? static_cast<T>(value_) : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return has_value() ? std::move(value_) : static_cast<T>(std::forward<U>(default_value));
return has_value() ? static_cast<T>(std::move(value_)) : static_cast<T>(std::forward<U>(default_value));
}
void drop_error() const {

View File

@ -11,11 +11,19 @@ TEST(OptionalTest, DefaultConstructor) {
optional<int> o;
EXPECT_FALSE(o.has_value());
EXPECT_FALSE(static_cast<bool>(o));
optional<int&> o1;
EXPECT_FALSE(o1.has_value());
EXPECT_FALSE(static_cast<bool>(o1));
}
TEST(OptionalTest, ConstructWithNullopt) {
optional<int> o(nullopt);
EXPECT_FALSE(o.has_value());
optional<int&> o1(nullopt);
EXPECT_FALSE(o1.has_value());
EXPECT_FALSE(static_cast<bool>(o1));
}
TEST(OptionalTest, ValueConstructor) {
@ -27,6 +35,13 @@ TEST(OptionalTest, ValueConstructor) {
optional<int> o2(x);
EXPECT_TRUE(o2.has_value());
EXPECT_EQ(o2.unwrap(), 100);
int y = 100;
optional<int&> o3(y);
EXPECT_TRUE(o3.has_value());
EXPECT_EQ(o3.unwrap(), 100);
y = 200;
EXPECT_EQ(o3.unwrap(), 200);
}
TEST(OptionalTest, CopyConstructor) {
@ -38,6 +53,17 @@ TEST(OptionalTest, CopyConstructor) {
optional<int> o3(nullopt);
optional<int> o4(o3);
EXPECT_FALSE(o4.has_value());
int y = 100;
optional<int&> o5(y);
optional<int&> o6(o5);
EXPECT_TRUE(o5.has_value());
EXPECT_EQ(o5.unwrap(), 100);
EXPECT_TRUE(o6.has_value());
EXPECT_EQ(o6.unwrap(), 100);
y = 200;
EXPECT_EQ(o5.unwrap(), 200);
EXPECT_EQ(o6.unwrap(), 200);
}
TEST(OptionalTest, MoveConstructor) {
@ -49,6 +75,14 @@ TEST(OptionalTest, MoveConstructor) {
optional<int> o3(nullopt);
optional<int> o4(std::move(o3));
EXPECT_FALSE(o4.has_value());
int y = 100;
optional<int&> o5(y);
optional<int&> o6(std::move(o5));
EXPECT_TRUE(o6.has_value());
EXPECT_EQ(o6.unwrap(), 100);
y = 200;
EXPECT_EQ(o6.unwrap(), 200);
}
TEST(OptionalTest, CopyAssignmentOperator) {
@ -62,6 +96,18 @@ TEST(OptionalTest, CopyAssignmentOperator) {
optional<int> o4(100);
o4 = o3;
EXPECT_FALSE(o4.has_value());
int y = 100;
optional<int&> o5(y);
optional<int&> o6;
o6 = o5;
EXPECT_TRUE(o5.has_value());
EXPECT_EQ(o5.unwrap(), 100);
EXPECT_TRUE(o6.has_value());
EXPECT_EQ(o6.unwrap(), 100);
y = 200;
EXPECT_EQ(o5.unwrap(), 200);
EXPECT_EQ(o6.unwrap(), 200);
}
TEST(OptionalTest, MoveAssignmentOperator) {
@ -75,6 +121,15 @@ TEST(OptionalTest, MoveAssignmentOperator) {
optional<int> o4(99);
o4 = std::move(o3);
EXPECT_FALSE(o4.has_value());
int y = 100;
optional<int&> o5(y);
optional<int&> o6;
o6 = std::move(o5);
EXPECT_TRUE(o6.has_value());
EXPECT_EQ(o6.unwrap(), 100);
y = 200;
EXPECT_EQ(o6.unwrap(), 200);
}
TEST(OptionalTest, AssignmentFromValue) {
@ -85,13 +140,31 @@ TEST(OptionalTest, AssignmentFromValue) {
o = nullopt;
EXPECT_FALSE(o.has_value());
optional<int&> o1;
int x = 100;
o1 = x;
EXPECT_TRUE(o1.has_value());
EXPECT_EQ(o1.unwrap(), x);
EXPECT_EQ(&o1.unwrap(), &x);
o1 = nullopt;
EXPECT_FALSE(o1.has_value());
}
TEST(OptionalTest, Reset) {
optional<int> o(42);
EXPECT_TRUE(o.has_value());
EXPECT_EQ(o.unwrap(), 42);
o.reset();
EXPECT_FALSE(o.has_value());
int x = 44;
optional<int&> o1(x);
EXPECT_TRUE(o1.has_value());
EXPECT_EQ(o1.unwrap(), 44);
o1.reset();
EXPECT_FALSE(o1.has_value());
}
TEST(OptionalTest, Swap) {
@ -111,6 +184,18 @@ TEST(OptionalTest, Swap) {
EXPECT_FALSE(o3.has_value());
EXPECT_TRUE(o4.has_value());
EXPECT_EQ(o4.unwrap(), 7);
int x = 20;
int y = 40;
optional<int&> o5 = x;
optional<int&> o6 = y;
EXPECT_EQ(o5.unwrap(), 20);
EXPECT_EQ(o6.unwrap(), 40);
o5.swap(o6);
EXPECT_EQ(o5.unwrap(), 40);
EXPECT_EQ(o6.unwrap(), 20);
EXPECT_EQ(x, 20);
EXPECT_EQ(y, 40);
}
TEST(OptionalTest, ValueOr) {
@ -119,6 +204,16 @@ TEST(OptionalTest, ValueOr) {
optional<int> o2(nullopt);
EXPECT_EQ(o2.value_or(100), 100);
int x = 20;
int y = 100;
optional<int&> o3(x);
EXPECT_EQ(o3.value_or(y), 20);
o3.reset();
EXPECT_EQ(o3.value_or(y), 100);
EXPECT_EQ(&o3.value_or(y), &y);
EXPECT_EQ(x, 20);
EXPECT_EQ(y, 100);
}
}

View File

@ -45,6 +45,11 @@ TEST_F(ResultFixture, ConstructWithValueLValue) {
s = "x";
EXPECT_EQ(result.unwrap_value(), "test");
cpptrace::detail::Result<std::string&, error> r2(s);
EXPECT_EQ(r2.unwrap_value(), "x");
s = "y";
EXPECT_EQ(r2.unwrap_value(), "y");
}
TEST_F(ResultFixture, ConstructWithErrorRValue) {
@ -71,10 +76,18 @@ TEST_F(ResultFixture, ConstructWithErrorLValue) {
TEST_F(ResultFixture, MoveConstructorValue) {
cpptrace::detail::Result<std::string, error> original(std::string("move"));
cpptrace::detail::Result<std::string, error> moved(std::move(original));
EXPECT_TRUE(moved.has_value());
EXPECT_EQ(moved.unwrap_value(), "move");
EXPECT_TRUE(original.has_value());
std::string s = "test";
cpptrace::detail::Result<std::string&, error> r1(s);
cpptrace::detail::Result<std::string&, error> r2(std::move(r1));
EXPECT_TRUE(r2.has_value());
EXPECT_EQ(r2.unwrap_value(), "test");
s = "foo";
EXPECT_EQ(r2.unwrap_value(), "foo");
EXPECT_TRUE(r2.has_value());
}
TEST_F(ResultFixture, MoveConstructorError) {
@ -97,6 +110,20 @@ TEST_F(ResultFixture, ValueOr) {
EXPECT_EQ(res_with_error.value_or(-1), -1);
EXPECT_EQ(std::move(res_with_error).value_or(-1), -1);
}
{
int x = 2;
int y = 3;
cpptrace::detail::Result<int&, error> res_with_value(x);
EXPECT_EQ(res_with_value.value_or(y), 2);
EXPECT_EQ(std::move(res_with_value).value_or(y), 2);
}
{
int x = 2;
cpptrace::detail::Result<int&, error> res_with_error(error{});
EXPECT_EQ(res_with_error.value_or(x), 2);
EXPECT_EQ(&res_with_error.value_or(x), &x);
EXPECT_EQ(std::move(res_with_error).value_or(x), 2);
}
}
}