Steve Downey
Two new views of zero or one element
views::nullable
nullable
views::maybe
nullable
bool
Dereferenceable
Things like pointers, std::optional, std::expected
Only safely dereferenceable if truthy
views::nullable
Adapt a nullable by lifting from the nullable monad to the ranges monad.
(you can safely ignore the M-word)
auto opt = possible_value(); if (opt) { // a few dozen lines ... use(*opt); // is *opt Safe ? }
for (auto&& opt : views::nullable(possible_value())) { // a few dozen lines ... use(opt); // opt is Safe }
views::maybe
A range
of zero or one elements
A view the same way views::single
is
–
O(1) with large C
Shows up in range comprehensions for guard clauses
[ (x, y, z) | z <- [1..], y <- [1..z], x <- [1..y], x^2 + y^2 == z^2]
yield_if
inline constexpr auto yield_if = [](bool b, auto x) { return b ? maybe_view{std::move(x)} : maybe_view<decltype(x)>{}; };
and_then
inline constexpr auto and_then = [](auto&& r, auto fun) { return decltype(r)(r) | std::ranges::views::transform(std::move(fun)) | std::ranges::views::join; };
using std::ranges::views::iota; auto triples = and_then(iota(1), [](int z) { return and_then(iota(1, z + 1), [=](int x) { return and_then(iota(x, z + 1), [=](int y) { return yield_if(x * x + y * y == z * z, std::make_tuple(x, y, z)); }); }); });
filter
Flattening a range of ranges excluding the empty range operates much like filter.
Different trade-offs.
Easier if the condition is not a simple property of the element.
The standard library should not be overly opinionated.
Useful as a return type for range code.
Provide fit and a polish
Is a range
Does not support assignment from underlying
Because there is no assignment from T there is no question about rebind/assign-through.
Assignment from views::maybe<T&> rebinds, preserving equality behavior.
There is specialized support for eliding the get
operation to make a maybe<std::reference_wrapper<T>> work directly.
If T& specialization is in place, it should be dropped and the disjucntion in the concept removed.
int i = 7; maybe_view<int> v1{}; ASSERT_TRUE(v1.size() == 0); maybe_view<int> v2{i}; ASSERT_TRUE(v2.size() == 1); for (auto i : v1) ASSERT_TRUE(i != i); // tautology so i is used and not warned for (auto i : v2) ASSERT_EQ(i, 7); int s = 4; for (auto&& i : views::maybe(s)) { ASSERT_EQ(i, 4); i = 9; ASSERT_EQ(i, 9); } ASSERT_EQ(s, 4);
int i = 7; maybe_view<int> v2{std::ref(i)}; for (auto i : v2) ASSERT_EQ(i, 7); int s = 4; for (auto&& i : views::maybe(std::ref(s))) { ASSERT_EQ(i, 4); i.get() = 9; ASSERT_EQ(i, 9); } ASSERT_EQ(s, 9);
std::optional s{7}; for (auto i : views::nullable(s)) ASSERT_EQ(i, 7); nullable_view e{std::optional<int>{}}; for (int i : e) ASSERT_TRUE(i != i);