Skip to content

Commit

Permalink
Require in_place_t with variadic unexpected ctors
Browse files Browse the repository at this point in the history
This makes unexpected ctors more consistent with the C++ standard
  • Loading branch information
jogerh committed Feb 19, 2025
1 parent 14c70a6 commit 260cea3
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 14 deletions.
31 changes: 17 additions & 14 deletions include/tl/expected.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,15 @@ template <class E> class unexpected {

template <class... Args, typename std::enable_if<std::is_constructible<
E, Args &&...>::value>::type * = nullptr>
constexpr explicit unexpected(Args &&...args)
constexpr explicit unexpected(in_place_t, Args &&...args)
: m_val(std::forward<Args>(args)...) {}

template <
class U, class... Args,
typename std::enable_if<std::is_constructible<
E, std::initializer_list<U> &, Args &&...>::value>::type * = nullptr>
constexpr explicit unexpected(std::initializer_list<U> l, Args &&...args)
constexpr explicit unexpected(in_place_t, std::initializer_list<U> l,
Args &&...args)
: m_val(l, std::forward<Args>(args)...) {}

constexpr const E &error() const & { return m_val; }
Expand Down Expand Up @@ -471,15 +473,15 @@ struct expected_storage_base {
detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
nullptr>
constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
: m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, std::forward<Args>(args)...), m_has_val(false) {}

template <class U, class... Args,
detail::enable_if_t<std::is_constructible<
E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
constexpr explicit expected_storage_base(unexpect_t,
std::initializer_list<U> il,
Args &&...args)
: m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, il, std::forward<Args>(args)...), m_has_val(false) {}

~expected_storage_base() {
if (m_has_val) {
Expand Down Expand Up @@ -518,15 +520,15 @@ template <class T, class E> struct expected_storage_base<T, E, true, true> {
detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
nullptr>
constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
: m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, std::forward<Args>(args)...), m_has_val(false) {}

template <class U, class... Args,
detail::enable_if_t<std::is_constructible<
E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
constexpr explicit expected_storage_base(unexpect_t,
std::initializer_list<U> il,
Args &&...args)
: m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, il, std::forward<Args>(args)...), m_has_val(false) {}

expected_storage_base(const expected_storage_base &) = default;
expected_storage_base(expected_storage_base &&) = default;
Expand Down Expand Up @@ -563,15 +565,15 @@ template <class T, class E> struct expected_storage_base<T, E, true, false> {
detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
nullptr>
constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
: m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, std::forward<Args>(args)...), m_has_val(false) {}

template <class U, class... Args,
detail::enable_if_t<std::is_constructible<
E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
constexpr explicit expected_storage_base(unexpect_t,
std::initializer_list<U> il,
Args &&...args)
: m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, il, std::forward<Args>(args)...), m_has_val(false) {}

expected_storage_base(const expected_storage_base &) = default;
expected_storage_base(expected_storage_base &&) = default;
Expand Down Expand Up @@ -612,15 +614,16 @@ template <class T, class E> struct expected_storage_base<T, E, false, true> {
detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
nullptr>
constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
: m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, std::forward<Args>(args)...), m_has_val(false) {}

template <class U, class... Args,
detail::enable_if_t<std::is_constructible<
E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
constexpr explicit expected_storage_base(unexpect_t,
std::initializer_list<U> il,
Args &&...args)
: m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, in_place, il, std::forward<Args>(args)...),
m_has_val(false) {}

expected_storage_base(const expected_storage_base &) = default;
expected_storage_base(expected_storage_base &&) = default;
Expand Down Expand Up @@ -656,15 +659,15 @@ template <class E> struct expected_storage_base<void, E, false, true> {
detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
nullptr>
constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
: m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, std::forward<Args>(args)...), m_has_val(false) {}

template <class U, class... Args,
detail::enable_if_t<std::is_constructible<
E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
constexpr explicit expected_storage_base(unexpect_t,
std::initializer_list<U> il,
Args &&...args)
: m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, il, std::forward<Args>(args)...), m_has_val(false) {}

expected_storage_base(const expected_storage_base &) = default;
expected_storage_base(expected_storage_base &&) = default;
Expand All @@ -690,15 +693,15 @@ template <class E> struct expected_storage_base<void, E, false, false> {
detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
nullptr>
constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
: m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, std::forward<Args>(args)...), m_has_val(false) {}

template <class U, class... Args,
detail::enable_if_t<std::is_constructible<
E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
constexpr explicit expected_storage_base(unexpect_t,
std::initializer_list<U> il,
Args &&...args)
: m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
: m_unexpect(in_place, il, std::forward<Args>(args)...), m_has_val(false) {}

expected_storage_base(const expected_storage_base &) = default;
expected_storage_base(expected_storage_base &&) = default;
Expand Down
115 changes: 115 additions & 0 deletions tests/constructors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <type_traits>
#include <vector>
#include <array>
#include <string>

struct takes_init_and_variadic {
Expand All @@ -13,6 +14,27 @@ struct takes_init_and_variadic {
: v(l), t(std::forward<Args>(args)...) {}
};

struct point {
int x;
int y;
point(int _x, int _y) : x{_x}, y{_y} {}
point(std::initializer_list<int> list) {
auto it = list.begin();
x = *it;
++it;
y = *it;
}
};

struct takes_init_and_variadic_trivial_dtor {
point p;
std::tuple<int, int> t;
template <class... Args>
takes_init_and_variadic_trivial_dtor(std::initializer_list<int> l,
Args &&...args)
: p(l), t(std::forward<Args>(args)...) {}
};

TEST_CASE("Constructors", "[constructors]") {
{
tl::expected<int,int> e;
Expand All @@ -38,6 +60,12 @@ TEST_CASE("Constructors", "[constructors]") {
REQUIRE(e == 42);
}

{
tl::expected<int, int> e(tl::in_place);
REQUIRE(e);
REQUIRE(e == 0);
}

{
tl::expected<std::vector<int>,int> e (tl::in_place, {0,1});
REQUIRE(e);
Expand All @@ -52,6 +80,40 @@ TEST_CASE("Constructors", "[constructors]") {
REQUIRE(std::get<1>(*e) == 1);
}

{
tl::expected<int, std::tuple<int, int>> e(tl::unexpect, 0, 1);
REQUIRE(!e);
REQUIRE(std::get<0>(e.error()) == 0);
REQUIRE(std::get<1>(e.error()) == 1);
}

{
tl::expected<void, std::tuple<int, int>> e(tl::unexpect, 0, 1);
REQUIRE(!e);
REQUIRE(std::get<0>(e.error()) == 0);
REQUIRE(std::get<1>(e.error()) == 1);
}
{
tl::expected<void, std::vector<int>> e(tl::unexpect, 2, 1);
REQUIRE(!e);
REQUIRE(e.error()[0] == 1);
REQUIRE(e.error()[1] == 1);
}
{
tl::expected<std::vector<int>, std::vector<int>> e(tl::unexpect, 2, 1);
REQUIRE(!e);
REQUIRE(e.error()[0] == 1);
REQUIRE(e.error()[1] == 1);
}
{
tl::expected<std::vector<int>, takes_init_and_variadic> e(tl::unexpect,
{0, 1}, 2, 3);
REQUIRE(!e);
REQUIRE(e.error().v[0] == 0);
REQUIRE(e.error().v[1] == 1);
REQUIRE(std::get<0>(e.error().t) == 2);
REQUIRE(std::get<1>(e.error().t) == 3);
}
{
tl::expected<takes_init_and_variadic,int> e (tl::in_place, {0,1}, 2, 3);
REQUIRE(e);
Expand All @@ -60,6 +122,51 @@ TEST_CASE("Constructors", "[constructors]") {
REQUIRE(std::get<0>(e->t) == 2);
REQUIRE(std::get<1>(e->t) == 3);
}
{
tl::expected<int, takes_init_and_variadic> e(tl::unexpect, {0, 1}, 2, 3);
REQUIRE(!e);
REQUIRE(e.error().v[0] == 0);
REQUIRE(e.error().v[1] == 1);
REQUIRE(std::get<0>(e.error().t) == 2);
REQUIRE(std::get<1>(e.error().t) == 3);
}
{
tl::expected<int, takes_init_and_variadic_trivial_dtor> e(tl::unexpect, {0, 1}, 2, 3);
REQUIRE(!e);
REQUIRE(e.error().p.x == 0);
REQUIRE(e.error().p.y == 1);
REQUIRE(std::get<0>(e.error().t) == 2);
REQUIRE(std::get<1>(e.error().t) == 3);
}
{
tl::expected<void, takes_init_and_variadic_trivial_dtor> e(tl::unexpect,
{0, 1}, 2, 3);
REQUIRE(!e);
REQUIRE(e.error().p.x == 0);
REQUIRE(e.error().p.y == 1);
REQUIRE(std::get<0>(e.error().t) == 2);
REQUIRE(std::get<1>(e.error().t) == 3);
}
{
tl::expected<int, std::vector<int>> e(tl::unexpect, 2, 1);
REQUIRE(!e);
REQUIRE(e.error()[0] == 1);
REQUIRE(e.error()[1] == 1);
}
{
tl::expected<std::vector<int>, point> e(tl::unexpect, 1, 2);
REQUIRE(!e);
REQUIRE(e.error().x == 1);
REQUIRE(e.error().y == 2);
}
{
tl::expected<void, takes_init_and_variadic> e(tl::unexpect, {0, 1}, 2, 3);
REQUIRE(!e);
REQUIRE(e.error().v[0] == 0);
REQUIRE(e.error().v[1] == 1);
REQUIRE(std::get<0>(e.error().t) == 2);
REQUIRE(std::get<1>(e.error().t) == 3);
}

{
tl::expected<int, int> e;
Expand Down Expand Up @@ -152,3 +259,11 @@ TEST_CASE("Constructors", "[constructors]") {
REQUIRE(e2.error().val == 's');
}
}

TEST_CASE("Unexpected constructors", "[constructors]") {
REQUIRE(tl::unexpected<int>(1).error() == 1);
REQUIRE(tl::unexpected<int>(tl::in_place).error() == 0);
REQUIRE(tl::unexpected<int>(tl::in_place, 1).error() == 1);
REQUIRE(tl::unexpected<std::vector<int>>(tl::in_place, {1, 2, 3}).error() ==
std::vector<int>{1, 2, 3});
}

0 comments on commit 260cea3

Please sign in to comment.