Skip to content

Commit 3b85a40

Browse files
authored
Merge pull request #226 from elbeno/add-latched
✨ Add `latched`
2 parents a95b340 + 7039002 commit 3b85a40

File tree

9 files changed

+206
-75
lines changed

9 files changed

+206
-75
lines changed

CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,16 @@ target_sources(
6767
include/stdx/intrusive_forward_list.hpp
6868
include/stdx/intrusive_list.hpp
6969
include/stdx/iterator.hpp
70+
include/stdx/latched.hpp
7071
include/stdx/memory.hpp
7172
include/stdx/numeric.hpp
7273
include/stdx/optional.hpp
7374
include/stdx/panic.hpp
7475
include/stdx/priority.hpp
7576
include/stdx/ranges.hpp
77+
include/stdx/rollover.hpp
7678
include/stdx/span.hpp
79+
include/stdx/static_assert.hpp
7780
include/stdx/tuple_algorithms.hpp
7881
include/stdx/tuple_destructure.hpp
7982
include/stdx/tuple.hpp

docs/index.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ include::functional.adoc[]
3131
include::intrusive_forward_list.adoc[]
3232
include::intrusive_list.adoc[]
3333
include::iterator.adoc[]
34+
include::latched.adoc[]
3435
include::memory.adoc[]
3536
include::numeric.adoc[]
3637
include::optional.adoc[]

docs/latched.adoc

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
== `latched.hpp`
3+
4+
A `latched` value represents a value that is computed on demand once, and
5+
latched (`const`) thereafter. It is constructed with a lambda expression that
6+
will compute the value when needed.
7+
8+
A `latched` value is similar to a xref:cached.adoc#_cached_hpp[`cached`] value except that
9+
once computed, a `latched` value cannot be reset.
10+
11+
[source,cpp]
12+
----
13+
constexpr auto c = stdx::latched{[] { return expensive_computation(); }};
14+
----
15+
16+
A `latched` value is something like a `std::optional` and supports some similar
17+
functionality. Note though that any kind of "dereference" operation
18+
automatically computes the value if needed.
19+
20+
[source,cpp]
21+
----
22+
// check whether the value is present
23+
auto b = c.has_value();
24+
25+
// or, automatic bool conversion (explicit)
26+
if (c) {
27+
// do something
28+
}
29+
30+
// use the value (computing where necessary)
31+
auto value = *c;
32+
auto alt_value = c.value();
33+
auto value_member = c->member;
34+
----
35+
36+
If needed, the type of the latched value can obtained with `latched_value_t`.
37+
38+
[source,cpp]
39+
----
40+
auto c = stdx::latched{[] { return expensive_computation(); }};
41+
using V = stdx::latched_value_t<decltype(c)>;
42+
----
43+
44+
NOTE: You can also use `typename decltype(c)::value_type`, but if the type of `c`
45+
has cvref qualifiers, `latched_value_t` saves the bother of using `remove_cvref_t`.

include/stdx/cached.hpp

+11-73
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,24 @@
11
#pragma once
22

33
#include <stdx/compiler.hpp>
4-
#include <stdx/functional.hpp>
4+
#include <stdx/latched.hpp>
55
#include <stdx/type_traits.hpp>
66

7-
#include <optional>
8-
#include <type_traits>
9-
#include <utility>
10-
117
namespace stdx {
128
inline namespace v1 {
13-
template <typename F> struct cached {
14-
using value_type = stdx::remove_cvref_t<std::invoke_result_t<F>>;
15-
16-
constexpr explicit cached(F const &f) : lazy{f} {}
17-
constexpr explicit cached(F &&f) : lazy{std::move(f)} {}
18-
19-
constexpr auto has_value() const noexcept -> bool {
20-
return opt.has_value();
21-
}
22-
constexpr explicit operator bool() const noexcept {
23-
return opt.has_value();
24-
}
25-
26-
constexpr auto value() & LIFETIMEBOUND -> value_type & {
27-
populate();
28-
return *opt;
29-
}
30-
constexpr auto value() const & LIFETIMEBOUND -> value_type const & {
31-
populate();
32-
return *opt;
33-
}
34-
constexpr auto value() && LIFETIMEBOUND -> value_type && {
35-
populate();
36-
return *std::move(opt);
37-
}
38-
constexpr auto value() const && LIFETIMEBOUND -> value_type const && {
39-
populate();
40-
return *std::move(opt);
41-
}
42-
43-
constexpr auto operator->() const LIFETIMEBOUND->value_type const * {
44-
populate();
45-
return opt.operator->();
46-
}
47-
constexpr auto operator->() LIFETIMEBOUND->value_type * {
48-
populate();
49-
return opt.operator->();
50-
}
9+
template <typename F> struct cached : latched<F> {
10+
using latched<F>::latched;
5111

52-
constexpr auto operator*() const & LIFETIMEBOUND->decltype(auto) {
53-
return value();
54-
}
55-
constexpr auto operator*() & LIFETIMEBOUND->decltype(auto) {
56-
return value();
12+
auto reset() { this->opt.reset(); }
13+
auto refresh() LIFETIMEBOUND -> typename latched<F>::value_type & {
14+
this->opt.reset();
15+
this->populate();
16+
return *this->opt;
5717
}
58-
constexpr auto operator*() const && LIFETIMEBOUND->decltype(auto) {
59-
return std::move(*this).value();
60-
}
61-
constexpr auto operator*() && LIFETIMEBOUND->decltype(auto) {
62-
return std::move(*this).value();
63-
}
64-
65-
auto reset() { opt.reset(); }
66-
auto refresh() LIFETIMEBOUND -> value_type & {
67-
opt.reset();
68-
populate();
69-
return *opt;
70-
}
71-
72-
private:
73-
constexpr auto populate() const {
74-
if (not opt.has_value()) {
75-
opt.emplace(lazy);
76-
}
77-
}
78-
79-
with_result_of<F> lazy;
80-
mutable std::optional<value_type> opt{};
8118
};
8219

83-
template <typename C>
84-
using cached_value_t = typename stdx::remove_cvref_t<C>::value_type;
20+
template <typename F> cached(F) -> cached<remove_cvref_t<F>>;
21+
22+
template <typename C> using cached_value_t = latched_value_t<C>;
8523
} // namespace v1
8624
} // namespace stdx

include/stdx/latched.hpp

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
3+
#include <stdx/compiler.hpp>
4+
#include <stdx/functional.hpp>
5+
#include <stdx/type_traits.hpp>
6+
7+
#include <optional>
8+
#include <type_traits>
9+
#include <utility>
10+
11+
namespace stdx {
12+
inline namespace v1 {
13+
template <typename F> struct latched {
14+
using value_type = stdx::remove_cvref_t<std::invoke_result_t<F>>;
15+
16+
constexpr explicit latched(F const &f) : lazy{f} {}
17+
constexpr explicit latched(F &&f) : lazy{std::move(f)} {}
18+
19+
constexpr auto has_value() const noexcept -> bool {
20+
return opt.has_value();
21+
}
22+
constexpr explicit operator bool() const noexcept {
23+
return opt.has_value();
24+
}
25+
26+
constexpr auto value() const & LIFETIMEBOUND -> value_type const & {
27+
populate();
28+
return *opt;
29+
}
30+
constexpr auto value() const && LIFETIMEBOUND -> value_type const && {
31+
populate();
32+
return *std::move(opt);
33+
}
34+
35+
constexpr auto operator->() const LIFETIMEBOUND->value_type const * {
36+
populate();
37+
return opt.operator->();
38+
}
39+
40+
constexpr auto operator*() const & LIFETIMEBOUND->decltype(auto) {
41+
return value();
42+
}
43+
constexpr auto operator*() const && LIFETIMEBOUND->decltype(auto) {
44+
return std::move(*this).value();
45+
}
46+
47+
protected:
48+
constexpr auto populate() const {
49+
if (not opt.has_value()) {
50+
opt.emplace(lazy);
51+
}
52+
}
53+
54+
with_result_of<F> lazy;
55+
mutable std::optional<value_type> opt{};
56+
};
57+
58+
template <typename C>
59+
using latched_value_t = typename stdx::remove_cvref_t<C>::value_type;
60+
} // namespace v1
61+
} // namespace stdx

include/stdx/rollover.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ template <typename T> struct rollover_t {
1212
using underlying_t = T;
1313

1414
constexpr rollover_t() = default;
15+
// NOLINTBEGIN(modernize-use-constraints)
1516
template <typename U,
1617
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
1718
constexpr explicit rollover_t(U u) : value{static_cast<underlying_t>(u)} {}
1819
template <typename U,
1920
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
2021
constexpr explicit rollover_t(rollover_t<U> u)
2122
: rollover_t{static_cast<U>(u)} {}
23+
// NOLINTEND(modernize-use-constraints)
2224

2325
[[nodiscard]] constexpr auto as_underlying() const -> underlying_t {
2426
return value;

test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ add_tests(
4848
intrusive_list_properties
4949
is_constant_evaluated
5050
iterator
51+
latched
5152
memory
5253
numeric
5354
optional

test/cached.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ TEST_CASE("non-movable value", "[cached]") {
9494
TEST_CASE("preserving value categories", "[cached]") {
9595
{
9696
auto c = stdx::cached{[] { return 42; }};
97-
static_assert(std::is_same_v<int &, decltype(*c)>);
98-
static_assert(std::is_same_v<int &&, decltype(*std::move(c))>);
97+
static_assert(std::is_same_v<int const &, decltype(*c)>);
98+
static_assert(std::is_same_v<int const &&, decltype(*std::move(c))>);
9999
}
100100
{
101101
auto const c = stdx::cached{[] { return 42; }};

test/latched.cpp

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include <stdx/latched.hpp>
2+
3+
#include <catch2/catch_test_macros.hpp>
4+
5+
TEST_CASE("construction", "[latched]") {
6+
auto c = stdx::latched{[] { return 42; }};
7+
CHECK(not c);
8+
CHECK(not c.has_value());
9+
}
10+
11+
TEST_CASE("exposed value_type", "[latched]") {
12+
auto c = stdx::latched{[] { return 42; }};
13+
static_assert(std::is_same_v<typename decltype(c)::value_type, int>);
14+
}
15+
16+
TEST_CASE("latched_value_t", "[latched]") {
17+
auto const c = stdx::latched{[] { return 42; }};
18+
static_assert(std::is_same_v<stdx::latched_value_t<decltype(c)>, int>);
19+
}
20+
21+
TEST_CASE("automatic population", "[latched]") {
22+
auto c = stdx::latched{[] { return 42; }};
23+
CHECK(c.value() == 42);
24+
CHECK(c.has_value());
25+
}
26+
27+
TEST_CASE("operator*", "[latched]") {
28+
auto c = stdx::latched{[] { return 42; }};
29+
CHECK(*c == 42);
30+
CHECK(c.has_value());
31+
}
32+
33+
namespace {
34+
struct S {
35+
int x{};
36+
};
37+
} // namespace
38+
39+
TEST_CASE("operator->", "[latched]") {
40+
auto c = stdx::latched{[] { return S{42}; }};
41+
CHECK(c->x == 42);
42+
CHECK(c.has_value());
43+
}
44+
45+
namespace {
46+
struct move_only {
47+
constexpr move_only(int i) : value{i} {}
48+
constexpr move_only(move_only &&) = default;
49+
int value{};
50+
};
51+
52+
struct non_movable {
53+
constexpr non_movable(int i) : value{i} {}
54+
constexpr non_movable(non_movable &&) = delete;
55+
int value{};
56+
};
57+
} // namespace
58+
59+
TEST_CASE("move-only value", "[latched]") {
60+
auto c = stdx::latched{[] { return move_only{42}; }};
61+
CHECK(c->value == 42);
62+
}
63+
64+
TEST_CASE("non-movable value", "[latched]") {
65+
auto c = stdx::latched{[] { return non_movable{42}; }};
66+
CHECK(c->value == 42);
67+
}
68+
69+
TEST_CASE("preserving value categories", "[latched]") {
70+
{
71+
auto c = stdx::latched{[] { return 42; }};
72+
static_assert(std::is_same_v<int const &, decltype(*c)>);
73+
static_assert(std::is_same_v<int const &&, decltype(*std::move(c))>);
74+
}
75+
{
76+
auto const c = stdx::latched{[] { return 42; }};
77+
static_assert(std::is_same_v<int const &, decltype(*c)>);
78+
static_assert(std::is_same_v<int const &&, decltype(*std::move(c))>);
79+
}
80+
}

0 commit comments

Comments
 (0)