Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,16 @@ target_sources(
include/stdx/intrusive_forward_list.hpp
include/stdx/intrusive_list.hpp
include/stdx/iterator.hpp
include/stdx/latched.hpp
include/stdx/memory.hpp
include/stdx/numeric.hpp
include/stdx/optional.hpp
include/stdx/panic.hpp
include/stdx/priority.hpp
include/stdx/ranges.hpp
include/stdx/rollover.hpp
include/stdx/span.hpp
include/stdx/static_assert.hpp
include/stdx/tuple_algorithms.hpp
include/stdx/tuple_destructure.hpp
include/stdx/tuple.hpp
Expand Down
1 change: 1 addition & 0 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ include::functional.adoc[]
include::intrusive_forward_list.adoc[]
include::intrusive_list.adoc[]
include::iterator.adoc[]
include::latched.adoc[]
include::memory.adoc[]
include::numeric.adoc[]
include::optional.adoc[]
Expand Down
45 changes: 45 additions & 0 deletions docs/latched.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

== `latched.hpp`

A `latched` value represents a value that is computed on demand once, and
latched (`const`) thereafter. It is constructed with a lambda expression that
will compute the value when needed.

A `latched` value is similar to a xref:cached.adoc#_cached_hpp[`cached`] value except that
once computed, a `latched` value cannot be reset.

[source,cpp]
----
constexpr auto c = stdx::latched{[] { return expensive_computation(); }};
----

A `latched` value is something like a `std::optional` and supports some similar
functionality. Note though that any kind of "dereference" operation
automatically computes the value if needed.

[source,cpp]
----
// check whether the value is present
auto b = c.has_value();

// or, automatic bool conversion (explicit)
if (c) {
// do something
}

// use the value (computing where necessary)
auto value = *c;
auto alt_value = c.value();
auto value_member = c->member;
----

If needed, the type of the latched value can obtained with `latched_value_t`.

[source,cpp]
----
auto c = stdx::latched{[] { return expensive_computation(); }};
using V = stdx::latched_value_t<decltype(c)>;
----

NOTE: You can also use `typename decltype(c)::value_type`, but if the type of `c`
has cvref qualifiers, `latched_value_t` saves the bother of using `remove_cvref_t`.
84 changes: 11 additions & 73 deletions include/stdx/cached.hpp
Original file line number Diff line number Diff line change
@@ -1,86 +1,24 @@
#pragma once

#include <stdx/compiler.hpp>
#include <stdx/functional.hpp>
#include <stdx/latched.hpp>
#include <stdx/type_traits.hpp>

#include <optional>
#include <type_traits>
#include <utility>

namespace stdx {
inline namespace v1 {
template <typename F> struct cached {
using value_type = stdx::remove_cvref_t<std::invoke_result_t<F>>;

constexpr explicit cached(F const &f) : lazy{f} {}
constexpr explicit cached(F &&f) : lazy{std::move(f)} {}

constexpr auto has_value() const noexcept -> bool {
return opt.has_value();
}
constexpr explicit operator bool() const noexcept {
return opt.has_value();
}

constexpr auto value() & LIFETIMEBOUND -> value_type & {
populate();
return *opt;
}
constexpr auto value() const & LIFETIMEBOUND -> value_type const & {
populate();
return *opt;
}
constexpr auto value() && LIFETIMEBOUND -> value_type && {
populate();
return *std::move(opt);
}
constexpr auto value() const && LIFETIMEBOUND -> value_type const && {
populate();
return *std::move(opt);
}

constexpr auto operator->() const LIFETIMEBOUND->value_type const * {
populate();
return opt.operator->();
}
constexpr auto operator->() LIFETIMEBOUND->value_type * {
populate();
return opt.operator->();
}
template <typename F> struct cached : latched<F> {
using latched<F>::latched;

constexpr auto operator*() const & LIFETIMEBOUND->decltype(auto) {
return value();
}
constexpr auto operator*() & LIFETIMEBOUND->decltype(auto) {
return value();
auto reset() { this->opt.reset(); }
auto refresh() LIFETIMEBOUND -> typename latched<F>::value_type & {
this->opt.reset();
this->populate();
return *this->opt;
}
constexpr auto operator*() const && LIFETIMEBOUND->decltype(auto) {
return std::move(*this).value();
}
constexpr auto operator*() && LIFETIMEBOUND->decltype(auto) {
return std::move(*this).value();
}

auto reset() { opt.reset(); }
auto refresh() LIFETIMEBOUND -> value_type & {
opt.reset();
populate();
return *opt;
}

private:
constexpr auto populate() const {
if (not opt.has_value()) {
opt.emplace(lazy);
}
}

with_result_of<F> lazy;
mutable std::optional<value_type> opt{};
};

template <typename C>
using cached_value_t = typename stdx::remove_cvref_t<C>::value_type;
template <typename F> cached(F) -> cached<remove_cvref_t<F>>;

template <typename C> using cached_value_t = latched_value_t<C>;
} // namespace v1
} // namespace stdx
61 changes: 61 additions & 0 deletions include/stdx/latched.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include <stdx/compiler.hpp>
#include <stdx/functional.hpp>
#include <stdx/type_traits.hpp>

#include <optional>
#include <type_traits>
#include <utility>

namespace stdx {
inline namespace v1 {
template <typename F> struct latched {
using value_type = stdx::remove_cvref_t<std::invoke_result_t<F>>;

constexpr explicit latched(F const &f) : lazy{f} {}
constexpr explicit latched(F &&f) : lazy{std::move(f)} {}

constexpr auto has_value() const noexcept -> bool {
return opt.has_value();
}
constexpr explicit operator bool() const noexcept {
return opt.has_value();
}

constexpr auto value() const & LIFETIMEBOUND -> value_type const & {
populate();
return *opt;
}
constexpr auto value() const && LIFETIMEBOUND -> value_type const && {
populate();
return *std::move(opt);
}

constexpr auto operator->() const LIFETIMEBOUND->value_type const * {
populate();
return opt.operator->();
}

constexpr auto operator*() const & LIFETIMEBOUND->decltype(auto) {
return value();
}
constexpr auto operator*() const && LIFETIMEBOUND->decltype(auto) {
return std::move(*this).value();
}

protected:
constexpr auto populate() const {
if (not opt.has_value()) {
opt.emplace(lazy);
}
}

with_result_of<F> lazy;
mutable std::optional<value_type> opt{};
};

template <typename C>
using latched_value_t = typename stdx::remove_cvref_t<C>::value_type;
} // namespace v1
} // namespace stdx
2 changes: 2 additions & 0 deletions include/stdx/rollover.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ template <typename T> struct rollover_t {
using underlying_t = T;

constexpr rollover_t() = default;
// NOLINTBEGIN(modernize-use-constraints)
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
constexpr explicit rollover_t(U u) : value{static_cast<underlying_t>(u)} {}
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
constexpr explicit rollover_t(rollover_t<U> u)
: rollover_t{static_cast<U>(u)} {}
// NOLINTEND(modernize-use-constraints)

[[nodiscard]] constexpr auto as_underlying() const -> underlying_t {
return value;
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ add_tests(
intrusive_list_properties
is_constant_evaluated
iterator
latched
memory
numeric
optional
Expand Down
4 changes: 2 additions & 2 deletions test/cached.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ TEST_CASE("non-movable value", "[cached]") {
TEST_CASE("preserving value categories", "[cached]") {
{
auto c = stdx::cached{[] { return 42; }};
static_assert(std::is_same_v<int &, decltype(*c)>);
static_assert(std::is_same_v<int &&, decltype(*std::move(c))>);
static_assert(std::is_same_v<int const &, decltype(*c)>);
static_assert(std::is_same_v<int const &&, decltype(*std::move(c))>);
}
{
auto const c = stdx::cached{[] { return 42; }};
Expand Down
80 changes: 80 additions & 0 deletions test/latched.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <stdx/latched.hpp>

#include <catch2/catch_test_macros.hpp>

TEST_CASE("construction", "[latched]") {
auto c = stdx::latched{[] { return 42; }};
CHECK(not c);
CHECK(not c.has_value());
}

TEST_CASE("exposed value_type", "[latched]") {
auto c = stdx::latched{[] { return 42; }};
static_assert(std::is_same_v<typename decltype(c)::value_type, int>);
}

TEST_CASE("latched_value_t", "[latched]") {
auto const c = stdx::latched{[] { return 42; }};
static_assert(std::is_same_v<stdx::latched_value_t<decltype(c)>, int>);
}

TEST_CASE("automatic population", "[latched]") {
auto c = stdx::latched{[] { return 42; }};
CHECK(c.value() == 42);
CHECK(c.has_value());
}

TEST_CASE("operator*", "[latched]") {
auto c = stdx::latched{[] { return 42; }};
CHECK(*c == 42);
CHECK(c.has_value());
}

namespace {
struct S {
int x{};
};
} // namespace

TEST_CASE("operator->", "[latched]") {
auto c = stdx::latched{[] { return S{42}; }};
CHECK(c->x == 42);
CHECK(c.has_value());
}

namespace {
struct move_only {
constexpr move_only(int i) : value{i} {}
constexpr move_only(move_only &&) = default;
int value{};
};

struct non_movable {
constexpr non_movable(int i) : value{i} {}
constexpr non_movable(non_movable &&) = delete;
int value{};
};
} // namespace

TEST_CASE("move-only value", "[latched]") {
auto c = stdx::latched{[] { return move_only{42}; }};
CHECK(c->value == 42);
}

TEST_CASE("non-movable value", "[latched]") {
auto c = stdx::latched{[] { return non_movable{42}; }};
CHECK(c->value == 42);
}

TEST_CASE("preserving value categories", "[latched]") {
{
auto c = stdx::latched{[] { return 42; }};
static_assert(std::is_same_v<int const &, decltype(*c)>);
static_assert(std::is_same_v<int const &&, decltype(*std::move(c))>);
}
{
auto const c = stdx::latched{[] { return 42; }};
static_assert(std::is_same_v<int const &, decltype(*c)>);
static_assert(std::is_same_v<int const &&, decltype(*std::move(c))>);
}
}