-
Notifications
You must be signed in to change notification settings - Fork 14
✨ Add latched
#226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
✨ Add latched
#226
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,6 +48,7 @@ add_tests( | |
| intrusive_list_properties | ||
| is_constant_evaluated | ||
| iterator | ||
| latched | ||
| memory | ||
| numeric | ||
| optional | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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))>); | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.