Skip to content

Commit 51e3c0e

Browse files
committed
Fully fix the forwarding problems in getset lens
The patch ensures the following requirements: 1) When `view(lens, whole)` is called: * the setter lambda is neither called nor constructed. It avoid unnecessary copies of the 'whole' object. * the `whole` object is directly forwarded into the getter lambda, without any copies. 3) When `set(lens, whole, part)` is called: * no getter is called at the toplevel lens (which value was previously discarded). * on the lower lenses, the getter receives an 'lvalue' reference to the `whole` object, and setter receives an 'rvalue' reference to the `whole` object. It ensures that there is no double-move problem. Fixes #160
1 parent 596983f commit 51e3c0e

File tree

2 files changed

+569
-23
lines changed

2 files changed

+569
-23
lines changed

lager/lenses.hpp

Lines changed: 108 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121
namespace lager {
2222
namespace detail {
2323

24+
template <typename T>
25+
struct should_move :
26+
std::integral_constant<bool,
27+
!std::is_array_v<T> &&
28+
!std::is_lvalue_reference_v<T>> {};
29+
30+
template <typename T>
31+
constexpr bool should_move_v = should_move<T>::value;
32+
2433
template <typename T>
2534
struct const_functor;
2635

@@ -42,6 +51,15 @@ struct const_functor
4251
}
4352
};
4453

54+
template <typename T>
55+
struct is_const_functor : public std::false_type {};
56+
57+
template <typename T>
58+
struct is_const_functor<const_functor<T>> : public std::true_type {};
59+
60+
template <typename T>
61+
constexpr bool is_const_functor_v = is_const_functor<T>::value;
62+
4563
template <typename T>
4664
struct identity_functor;
4765

@@ -59,40 +77,111 @@ struct identity_functor
5977
template <typename Fn>
6078
auto operator()(Fn&& f) &&
6179
{
62-
return make_identity_functor(
63-
std::forward<Fn>(f)(std::forward<T>(value)));
80+
81+
if constexpr (!should_move_v<T>) {
82+
return make_identity_functor(
83+
std::forward<Fn>(f)(value));
84+
} else {
85+
return make_identity_functor(
86+
std::forward<Fn>(f)(std::move(value)));
87+
}
6488
}
6589
};
6690

6791
template <typename Part>
6892
struct identity_functor_skip_first
6993
{
7094
template <typename T>
71-
auto operator() (T&&) const {
95+
auto operator() (T&&) const & {
7296
return make_identity_functor(part);
7397
}
7498

75-
Part &part;
99+
template <typename T>
100+
auto operator() (T&&) && {
101+
if constexpr (!should_move_v<Part>) {
102+
return make_identity_functor(part);
103+
} else {
104+
return make_identity_functor(std::move(part));
105+
}
106+
}
107+
108+
Part part;
76109
};
77110

78111
template <typename T>
79-
auto make_identity_functor_skip_first(T&& x) -> identity_functor_skip_first<std::remove_reference_t<T>>
112+
auto make_identity_functor_skip_first(T&& x) -> identity_functor_skip_first<T>
80113
{
81114
return {std::forward<T>(x)};
82115
}
83116

84-
template <typename Func, typename Getter, typename Whole>
85-
auto call_getter_or_skip(const Func& func, Getter&& getter, Whole&& whole) {
86-
return func(LAGER_FWD(getter)(LAGER_FWD(whole)));
87-
}
117+
template <typename F, typename Getter, typename Setter>
118+
struct getset_t
119+
{
120+
template <typename Whole>
121+
auto operator() (Whole &&w) {
122+
if constexpr (is_const_functor_v<decltype(f(getter(LAGER_FWD(w))))>) {
123+
/**
124+
* We don't have a setter here, so it is safe to
125+
* jus pass `w` as an rvalue.
126+
*
127+
* We also know that we are calling the const_functor,
128+
* and it discards the passed argument, so just pass a
129+
* noop to it.
130+
*
131+
* This branch is taken when viewing through the lens.
132+
*/
133+
return f(getter(LAGER_FWD(w))) // pass `w` into the getter as an rvalue!
134+
(zug::noop);
135+
} else {
136+
/**
137+
* Here we have both, getter and setter, so we pass
138+
* `w` to getter as an lvalue, and to setter as an rvalue.
139+
* The setter has a chance to reuse the resources of
140+
* the passed value.
141+
*
142+
* This branch is taken on all the levels of setting the
143+
* value through except of the tompost level.
144+
*/
145+
return f(getter(w)) // pass `w` into the getter as an lvalue!
146+
([&](auto&& x) {
147+
return setter(LAGER_FWD(w), LAGER_FWD(x));
148+
});
149+
}
150+
}
151+
152+
F f;
153+
Getter getter;
154+
Setter setter;
155+
};
156+
157+
/**
158+
* This specialization is called when a set() method is called over
159+
* the lens. In such a case we can skip calling the getter branch
160+
* of the lens.
161+
*
162+
* This branch is taken on the topmost level of setting the value
163+
* through the lens.
164+
*/
165+
template <typename T, typename Getter, typename Setter>
166+
struct getset_t<identity_functor_skip_first<T>, Getter, Setter>
167+
{
168+
template <typename Part>
169+
auto operator() (Part &&p) {
170+
return std::move(f)(zug::noop)
171+
([&](auto&& x) {
172+
return setter(LAGER_FWD(p), LAGER_FWD(x));
173+
});
174+
}
175+
176+
identity_functor_skip_first<T> &&f;
177+
Getter getter;
178+
Setter setter;
179+
};
88180

89-
template <typename Getter, typename Part, typename Whole>
90-
auto call_getter_or_skip(const identity_functor_skip_first<Part>& func, Getter&&, Whole&&) {
91-
/**
92-
* When set() call is being executed, we should not execute the setter,
93-
* the value will be dropped later anyway
94-
*/
95-
return func(*static_cast<Part*>(nullptr));
181+
template <typename F, typename Getter, typename Setter>
182+
auto make_getset_t(F &&f, const Getter &getter, const Setter &setter)
183+
{
184+
return getset_t<F, Getter, Setter>{std::forward<F>(f), getter, setter};
96185
}
97186

98187
} // namespace detail
@@ -112,7 +201,7 @@ decltype(auto) view(LensT&& lens, T&& x)
112201
template <typename LensT, typename T, typename U>
113202
decltype(auto) set(LensT&& lens, T&& x, U&& v)
114203
{
115-
return lens(detail::make_identity_functor_skip_first(v)) (
204+
return lens(detail::make_identity_functor_skip_first(std::forward<decltype(v)>(v))) (
116205
std::forward<T>(x))
117206
.value;
118207
}
@@ -135,14 +224,10 @@ namespace lenses {
135224
//! @{
136225

137226
template <typename Getter, typename Setter>
138-
auto getset(Getter&& getter, Setter&& setter)
227+
auto getset(const Getter& getter, const Setter& setter)
139228
{
140229
return zug::comp([=](auto&& f) {
141-
return [&, f = LAGER_FWD(f)](auto&& p) {
142-
return detail::call_getter_or_skip(f, getter, LAGER_FWD(p))([&](auto&& x) {
143-
return setter(LAGER_FWD(p), LAGER_FWD(x));
144-
});
145-
};
230+
return detail::make_getset_t(LAGER_FWD(f), getter, setter);
146231
});
147232
}
148233

0 commit comments

Comments
 (0)