Skip to content

Commit a65d57c

Browse files
committed
🎨 Make call_by_need deal with functions returning references
Problem: - If `call_by_need` calls a function returning a reference, it decays the result into the results tuple. It should preserve lvalue references generated in this way. Solution: - Preserve lvalue references in the results tuple.
1 parent 316da30 commit a65d57c

File tree

3 files changed

+39
-13
lines changed

3 files changed

+39
-13
lines changed

docs/call_by_need.adoc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
`call_by_need` is a function that takes a xref:tuple.adoc#_tuple_hpp[tuple] of
55
functions and a tuple of arguments, and applies the functions to the arguments
6-
as needed. It returns a tuple of results, with any unused arguments forwarded as
7-
if by a call to
8-
https://en.cppreference.com/w/cpp/utility/functional/identity.html[`std::identity`].
6+
as needed. It returns a tuple of results, with any unused arguments also sent along.
97

108
NOTE: `call_by_need` is available only in C++20 and later.
119

@@ -33,16 +31,22 @@ auto args = stdx::tuple{arg<0>{}};
3331
auto r = stdx::call_by_need(funcs, args); // tuple{17, 42}
3432
----
3533

36-
If arguments are unused, they are treated as if `std::identity` was appended to
37-
the set of functions:
34+
If arguments are unused, they are treated as if by a call to `safe_forward`:
3835
[source,cpp]
3936
----
37+
template <typename T>
38+
constexpr auto safe_forward(T&& t) -> T { return t; }
39+
4040
auto funcs = stdx::tuple{[] (arg<0>) { return 17; }};
4141
auto args = stdx::tuple{arg<0>{}, arg<1>{}}; // arg<1>{} is unused
4242
4343
auto r = stdx::call_by_need(funcs, args); // tuple{17, arg<1>{}}
4444
----
4545

46+
NOTE: `safe_forward` forwards rvalue references so that they are
47+
moved into the results tuple as owned values, but preserves lvalue references so
48+
that they appear in the results tuple as lvalue references.
49+
4650
Arguments and functions do not have to be in corresponding order; the results will be
4751
in the same order as the functions.
4852
[source,cpp]

include/stdx/call_by_need.hpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,10 @@
99
#include <algorithm>
1010
#include <array>
1111
#include <cstddef>
12-
#include <functional>
1312
#include <iterator>
1413
#include <type_traits>
1514
#include <utility>
1615

17-
template <auto...> struct undef_v;
18-
template <typename...> struct undef_t;
19-
2016
namespace stdx {
2117
inline namespace v1 {
2218
namespace cbn_detail {
@@ -145,6 +141,10 @@ template <typename... Fs> struct by_need {
145141
return concat(given_calls, extra_calls);
146142
}
147143
};
144+
145+
struct safe_forward {
146+
template <typename T> constexpr auto operator()(T &&t) -> T { return t; }
147+
};
148148
} // namespace cbn_detail
149149

150150
template <tuplelike Fs, tuplelike Args>
@@ -160,13 +160,18 @@ constexpr auto call_by_need(Fs &&fs, Args &&args) {
160160
std::make_index_sequence<tuple_size_v<remove_cvref_t<Args>>>{});
161161

162162
auto new_fs = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
163-
return tuple{get<Is>(std::forward<Fs>(fs))..., std::identity{}};
163+
return tuple{get<Is>(std::forward<Fs>(fs))...,
164+
cbn_detail::safe_forward{}};
164165
}(std::make_index_sequence<tuple_size_v<Fs>>{});
165166

166167
auto ret = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
167-
return tuple{cbn_detail::invoke<calls[Is].arg_base, calls[Is].arg_len>(
168-
get<calls[Is].fn_idx>(std::move(new_fs)),
169-
std::forward<Args>(args))...};
168+
return tuple<
169+
decltype(cbn_detail::invoke<calls[Is].arg_base, calls[Is].arg_len>(
170+
get<calls[Is].fn_idx>(std::move(new_fs)),
171+
std::forward<Args>(args)))...>{
172+
cbn_detail::invoke<calls[Is].arg_base, calls[Is].arg_len>(
173+
get<calls[Is].fn_idx>(std::move(new_fs)),
174+
std::forward<Args>(args))...};
170175
}(std::make_index_sequence<calls.size()>{});
171176
return stdx::filter<cbn_detail::is_nonvoid_t>(std::move(ret));
172177
}

test/call_by_need.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ TEST_CASE("move-only return", "[call_by_need]") {
138138
STATIC_REQUIRE(get<0>(r).value == 17);
139139
}
140140

141+
TEST_CASE("move-only function", "[call_by_need]") {
142+
constexpr auto r = stdx::call_by_need(
143+
stdx::tuple{[m = move_only{17}](int) mutable { return std::move(m); }},
144+
stdx::tuple{17});
145+
STATIC_REQUIRE(std::is_same_v<decltype(r), stdx::tuple<move_only> const>);
146+
STATIC_REQUIRE(get<0>(r) == move_only{17});
147+
}
148+
141149
TEST_CASE("converted arguments", "[call_by_need]") {
142150
auto const r = stdx::call_by_need(stdx::tuple{[](char c) {
143151
CHECK(c == 'a');
@@ -197,3 +205,12 @@ TEST_CASE("no functions given", "[call_by_need]") {
197205
STATIC_REQUIRE(std::is_same_v<decltype(r), stdx::tuple<int> const>);
198206
STATIC_REQUIRE(get<0>(r) == 17);
199207
}
208+
209+
TEST_CASE("function returning reference", "[call_by_need]") {
210+
int value{17};
211+
auto const r = stdx::call_by_need(
212+
stdx::tuple{[&](arg_t<0>) -> int & { return value; }},
213+
stdx::tuple{arg<0>});
214+
STATIC_REQUIRE(std::is_same_v<decltype(r), stdx::tuple<int &> const>);
215+
CHECK(get<0>(r) == 17);
216+
}

0 commit comments

Comments
 (0)