Skip to content

Commit 82a4b9b

Browse files
authored
Add discard functor (#162)
* Add discard functor * Add simple examples for discard * Add docs for discard * Add discard tests
1 parent 6275cd6 commit 82a4b9b

File tree

10 files changed

+371
-7
lines changed

10 files changed

+371
-7
lines changed

docs/expected/discard.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: fn::discard
3+
---
4+
5+
##### Defined in {style: "api", badge: "#include <fn/discard.hpp>"}
6+
7+
---
8+
9+
:include-doxygen-doc: fn::discard_t
10+
11+
---
12+
13+
## Call signatures {style: "api"}
14+
:include-doxygen-member: fn::discard_t::operator() { signatureOnly: false, includeAllMatches: true }
15+
16+
---
17+
18+
## Return value {style: "api"}
19+
void
20+
21+
---
22+
23+
## Examples {style: "api"}
24+
25+
:include-template: templates/snippet.md {
26+
path: "examples/simple.cpp",
27+
surroundedBy: ["// example-error-struct", "// example-expected-discard"],
28+
desc: "`42` is observed by `inspect` and the value is discarded by `discard` (no warning for discarded result of `inspect`)."
29+
}

docs/optional/discard.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: fn::discard
3+
---
4+
5+
##### Defined in {style: "api", badge: "#include <fn/discard.hpp>"}
6+
7+
---
8+
9+
:include-doxygen-doc: fn::discard_t
10+
11+
---
12+
13+
## Call signatures {style: "api"}
14+
:include-doxygen-member: fn::discard_t::operator() { signatureOnly: false, includeAllMatches: true }
15+
16+
---
17+
18+
## Return value {style: "api"}
19+
void
20+
21+
---
22+
23+
## Examples {style: "api"}
24+
25+
:include-template: templates/snippet.md {
26+
path: "examples/simple.cpp",
27+
surroundedBy: ["// example-error-struct", "// example-optional-discard"],
28+
desc: "`42` is observed by `inspect` and the value is discarded by `discard` (no warning for discarded result of `inspect`)."
29+
}

docs/toc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
expected
22
and_then
3+
discard
34
fail
45
filter
56
inspect_error
@@ -11,6 +12,7 @@ expected
1112
value_or
1213
optional
1314
and_then
15+
discard
1416
fail
1517
filter
1618
inspect_error

include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ set(INCLUDE_FN_HEADERS
1717
fn/and_then.hpp
1818
fn/choice.hpp
1919
fn/concepts.hpp
20+
fn/discard.hpp
2021
fn/expected.hpp
2122
fn/fail.hpp
2223
fn/filter.hpp

include/fn/detail/fwd.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace fn {
1212

1313
// functors
1414
struct and_then_t;
15+
struct discard_t;
1516
struct transform_t;
1617
struct transform_error_t;
1718
struct or_else_t;

include/fn/discard.hpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2025 Bronek Kozicki, Alex Kremer
2+
//
3+
// Distributed under the ISC License. See accompanying file LICENSE.md
4+
// or copy at https://opensource.org/licenses/ISC
5+
6+
#ifndef INCLUDE_FUNCTIONAL_DISCARD
7+
#define INCLUDE_FUNCTIONAL_DISCARD
8+
9+
#include <fn/concepts.hpp>
10+
#include <fn/functor.hpp>
11+
12+
#include <concepts>
13+
#include <type_traits>
14+
#include <utility>
15+
16+
namespace fn {
17+
/**
18+
* @brief Discard the value explicitly
19+
*
20+
* This is useful when only side-effects are desired and the final value is not needed.
21+
*
22+
* Use through the `fn::discard` nielbloid.
23+
*/
24+
constexpr inline struct discard_t final {
25+
/**
26+
* @brief Unconditionally discards the value
27+
* @return A functor that explicitly discards the value
28+
*/
29+
[[nodiscard]] constexpr auto operator()() const noexcept -> functor<discard_t> { return {}; }
30+
31+
struct apply final {
32+
static constexpr auto operator()(some_monadic_type auto &&) noexcept -> void {}
33+
};
34+
} discard = {};
35+
36+
} // namespace fn
37+
38+
#endif // INCLUDE_FUNCTIONAL_DISCARD

include/fn/functor.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ template <typename Functor, typename... Args> struct functor final {
3939
using data_t = pack<as_value_t<Args>...>;
4040
data_t data;
4141

42-
static_assert(sizeof...(Args) > 0); // NOTE Consider relaxing
4342
static_assert(std::is_empty_v<functor_type> && std::is_empty_v<functor_apply>
4443
&& std::is_default_constructible_v<functor_type> && std::is_default_constructible_v<functor_apply>);
4544

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ set(TESTS_FN_SOURCES
2222
fn/and_then.cpp
2323
fn/choice.cpp
2424
fn/concepts.cpp
25+
fn/discard.cpp
2526
fn/expected.cpp
2627
fn/fail.cpp
2728
fn/filter.cpp

tests/examples/simple.cpp

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// or copy at https://opensource.org/licenses/ISC
55

66
#include <fn/and_then.hpp>
7+
#include <fn/discard.hpp>
78
#include <fn/fail.hpp>
89
#include <fn/filter.hpp>
910
#include <fn/inspect.hpp>
@@ -114,10 +115,19 @@ TEST_CASE("Minimal expected", "[expected][and_then]")
114115
REQUIRE(value.error().what == "Less than 42");
115116
// example-expected-filter-error
116117
}
118+
{
119+
// example-expected-discard
120+
fn::expected<int, Error> ex{42};
121+
122+
// Observe for side-effects only
123+
ex | fn::inspect([](int v) noexcept { REQUIRE(v == 42); })
124+
| fn::discard();
125+
// example-expected-discard
126+
}
117127
// clang-format on
118128
}
119129

120-
TEST_CASE("Demo expected", "[expected][pack][and_then][transform_error][transform][inspect][inspect_error]["
130+
TEST_CASE("Demo expected", "[expected][pack][and_then][discard][transform_error][transform][inspect][inspect_error]["
121131
"recover][fail][filter][immovable]")
122132
{
123133
constexpr auto fn1 = [](char const *str, double &peek) {
@@ -219,6 +229,16 @@ TEST_CASE("Demo expected", "[expected][pack][and_then][transform_error][transfor
219229
auto const p4 = fn3("42", "12");
220230
CHECK(p4.has_value());
221231
CHECK(p4.value() == 42 * 12);
232+
233+
fn::expected<int, Error>{42} //
234+
| fn::inspect([](int v) noexcept { REQUIRE(v == 42); }) //
235+
| fn::inspect_error([](Error) noexcept { CHECK(false); }) //
236+
| fn::discard();
237+
238+
fn::expected<int, Error>{std::unexpected<Error>{"discarded"}} //
239+
| fn::inspect([](int) noexcept { CHECK(false); }) //
240+
| fn::inspect_error([](Error e) noexcept { REQUIRE(e.what == "discarded"); }) //
241+
| fn::discard();
222242
}
223243

224244
TEST_CASE("Minimal optional", "[optional][and_then]")
@@ -250,28 +270,37 @@ TEST_CASE("Minimal optional", "[optional][and_then]")
250270
}
251271
{
252272
// example-optional-filter-value
253-
fn::optional<int> ex{42};
273+
fn::optional<int> op{42};
254274

255-
auto value = ex
275+
auto value = op
256276
| fn::filter([](auto &&i) { return i >= 42; }); // Filter out values less than 42
257277

258278
REQUIRE(value.value() == 42);
259279
// example-optional-filter-value
260280
}
261281
{
262282
// example-optional-filter-empty
263-
fn::optional<int> ex{12};
283+
fn::optional<int> op{12};
264284

265-
auto value = ex
285+
auto value = op
266286
| fn::filter([](auto &&i) { return i >= 42; });
267287

268288
REQUIRE(not value.has_value());
269289
// example-optional-filter-empty
270290
}
291+
{
292+
// example-optional-discard
293+
fn::optional<int> op{42};
294+
295+
// Observe for side-effects only
296+
op | fn::inspect([](int v) noexcept { REQUIRE(v == 42); })
297+
| fn::discard();
298+
// example-optional-discard
299+
}
271300
// clang-format on
272301
}
273302

274-
TEST_CASE("Demo optional", "[optional][pack][and_then][or_else][inspect][transform][fail][filter][recover]")
303+
TEST_CASE("Demo optional", "[optional][pack][and_then][discard][or_else][inspect][transform][fail][filter][recover]")
275304
{
276305
constexpr auto fn1 = [](char const *str, int &peek) {
277306
using namespace fn;
@@ -359,6 +388,10 @@ TEST_CASE("Demo optional", "[optional][pack][and_then][or_else][inspect][transfo
359388
auto const p4 = fn3("42", "12");
360389
CHECK(p4.has_value());
361390
CHECK(p4.value() == 42 * 12);
391+
392+
fn::optional<int>{42} //
393+
| fn::inspect([](int v) noexcept { REQUIRE(v == 42); }) //
394+
| fn::discard();
362395
}
363396

364397
TEST_CASE("Demo choice and graded monad", "[choice][and_then][inspect][transform][graded]")

0 commit comments

Comments
 (0)