Skip to content

Commit 0f3fce2

Browse files
snarkmasterfacebook-github-bot
authored andcommitted
Bind.md reflects naming & policy updates
Reviewed By: ispeters Differential Revision: D78935262 fbshipit-source-id: d9344219ee8a600d72f711a652f61df13110fa03
1 parent 1474e0b commit 0f3fce2

1 file changed

Lines changed: 74 additions & 49 deletions

File tree

folly/lang/bind/Bind.md

Lines changed: 74 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Binding API inputs to storage with `folly/lang/bind/`
22

3-
## User guide
3+
## For users
44

55
Are you trying to call a `folly::bind`-enabled API? For simple usage,
66
you should not need to read this file at all! Read the API docs instead.
@@ -9,37 +9,52 @@ NEVER write functions that pass `folly::bind` helper types (`constant`,
99
`const_ref`, `args`, etc) by reference. These immovable objects must
1010
only exist in the statement that constructed them.
1111

12-
The high-level idea of `folly::bind` is to offer **the caller** a
13-
vocabulary to describe the storage types to be used by the callee.
12+
Does `folly::bind::some_word` feel too long? It is fine to write this in any
13+
`.cpp` file, or inside your project-level namespace:
1414

15-
For example, if a callee wants to store a generic tuple, the caller may
16-
write `constant{5}, const_ref{b, c}, mut_ref{d}` to define the storage as
17-
`std::tuple<const int, const int&, const int&, int&>{5, b, c, d}`.
15+
namespace bind = folly::bind;
16+
17+
The high-level idea of `folly::bind` is to offer **the caller** a vocabulary to
18+
describe the storage types to be used by the callee.
19+
20+
For example, if `foo()` wants to store a generic tuple, its caller may write:
21+
22+
foo(bind::constant{5}, bind::const_ref{b, c}, bind::mut_ref{d})
23+
24+
That tells `foo()` to store:
25+
26+
std::tuple<const int, const int&, const int&, int&>{5, b, c, d}
1827

1928
Your specific API's docs are authoritative -- a callee can change modifier
2029
meanings, or define new ones. That said, suggested modifier semantics are:
30+
2131
- Pass non-movable, non-copyable types via `bind::in_place<T>()` or
2232
`bind::in_place_with(fn)`.
23-
- The API decides whether `const_ref` / `mut_ref` are supported.
33+
34+
- The API decides whether to support `bind::const_ref` / `bind::mut_ref`.
2435
If NOT, then the callee's signature decides between by-value & by-ref.
25-
* Using `const_ref` etc should cause a compile error (`static_assert`).
26-
If YES, they tell the callee to take the argument by reference. Then:
27-
* WATCH OUT: `const_ref` / `mut_ref` is explicit unlike regular C++.
36+
* Using `bind::const_ref` etc should be a compile error (`static_assert`).
37+
If YES, these tell the callee to take the argument by reference.
38+
* WATCH OUT: Unlike plain C++, `bind::const_ref` / `bind::mut_ref` is
39+
explicit about `const`-ness.
40+
2841
Rationale: Programming best practice is to minimize "implicit"
2942
mutation. When writing modern C++, two argument passing styles
3043
predominate: `T` -- you own this, and `const T&` -- a read-only
3144
input. Any mutable "output" references, like `T&`, ought to be
3245
plainly visible at the callsite to avoid bugs, and `mut_ref` is.
46+
3347
* Passing a variable without modifiers will be pass-by-value.
48+
3449
- Unlike `std::as_const`, which changes the `const`ness of the input,
3550
`bind::constant` / `bind::mut` say whether the *destination* is `const`.
3651
So, `constant` is like `const T var` in the callee's signature. E.g.
37-
* `constant(std::move(var))` moves in the value, and stores it as `const`.
38-
* `mut` never removes a `const` qualifier from the underlying
39-
data. Rather, it can override the "references default to `const`"
40-
behavior, or to override a `constant` modifier that it surrounds.
52+
* `bind::constant(std::move(v))` moves in `v`, and stores it as `const`.
53+
* `bind::mut` never removes a `const` qualifier from the underlying data.
54+
It can override the "references default to `const`" behavior, or a
55+
`bind::constant` modifier that it surrounds.
4156

42-
## Library authors only
57+
## For library authors
4358

4459
### What is this for?
4560

@@ -55,8 +70,8 @@ These options have tradeoffs, but none provide ALL of these features:
5570
- Let the caller set the target storage (value or ref, const or not) for
5671
the binding, with the callee just specifying just `auto`. Consequences:
5772
* The callee can reflect on the supplied args, without C++26 P2996
58-
* `folly::bind::constant` lets you move a non-`const` object into
59-
`const` storage, while `std::as_const` cannot.
73+
* `bind::constant` lets you move a non-`const` object into `const`
74+
storage, while `std::as_const` cannot.
6075
- Define custom binding logic for some args, like `as_capture` in
6176
`async_closure`, or named arguments in `folly/lang/named`.
6277
- Allow helper functions to return several adjacent arguments -- as if
@@ -72,9 +87,15 @@ uniformly bind:
7287

7388
Consider this `folly::bind` expression:
7489

75-
bind::args ba{5, constant{a}, mut_ref{b, std::move(c)}, const_ref{d}};
90+
bind::args ba{
91+
5,
92+
bind::constant{a},
93+
bind::mut_ref{b, std::move(c)},
94+
bind::const_ref{d}};
7695

77-
Stored with the default `bind_to_storage_policy`, this is akin to:
96+
With the default `bind_to_storage_policy`, it implies the following storage
97+
types (but not the outer `std::tuple` -- the algorithm taking `ba` must
98+
construct it -- feel free to add `lang/bind/ToTuple.h`).
7899

79100
std::tuple<int, const A, B&, C&&, const D&> tup =
80101
std::forward_as_tuple{5, a, b, std::move(c), d};
@@ -83,7 +104,7 @@ Regular C++ arguments are fine (and preferred!) when the destination types
83104
are known in advance, and the types are movable. But, in trickier cases
84105
`folly::bind` saves the day:
85106
- It lets the caller ergonomically declare a structure at the same time it
86-
is constructed or passed (`async_closure`, named scopes).
107+
is constructed or passed (`async_closure`, `safe_closure`, named scopes).
87108
- In order to in-place construct an immovable type `A` by-value inside the
88109
caller's storage, you need an implicit conversion operator.
89110
`bind::in_place*` implements one on your behalf.
@@ -93,21 +114,25 @@ are known in advance, and the types are movable. But, in trickier cases
93114
### What changes in the UX over standard C++ bindings?
94115

95116
That depends on how you integrate `folly::bind`, but... the recommended
96-
way is to use the standard `bind_to_storage_policy`, possibly extending it in a
97-
careful way. The goal should be "low user surprisal", so we stay close to
117+
way is to use a standard policy, possibly with careful extensions. Namely:
118+
- `bind_to_storage_policy` when the callee wants `bind::` verbs to modulate
119+
how the `bind::args` are stored -- see the `std::tuple` example above.
120+
- `bind_as_argument` when the callee wants `bind::` verbs to change how
121+
its internally-stored values are bound to a function argument, as in
122+
`safe_closure()`.
123+
124+
The goal should be "low user surprisal", so the standard policies stay close to
98125
standard C++ semantics, except for a couple of restrictions to make argument
99126
passing less bug-prone.
100127

101-
With the vanilla `bind_to_storage_policy`, besides `bind::in_place*` support, you get:
102-
- Arguments are bound by value, unless the callsite includes `const_ref` /
103-
`mut_ref`. This prevents bugs from callers not expecting aliasing.
104-
- While `copy{}` and `move{}` modifiers aren't yet provided, `category_t`
105-
and `bind_to_storage_policy` allow for the notion of the caller restricting that
106-
an argument be passed by copy, or move-copy.
128+
For example, with the standard `bind_to_storage_policy`, you get
129+
`bind::in_place*` support, you and this unsurprising behavior:
130+
- Arguments are stored as values, unless the callsite has `bind::const_ref` /
131+
/ `bind::mut_ref`. This prevents bugs from callers not expecting aliasing.
107132

108133
In "synchronous" APIs, where your code only runs while the user's original
109-
statement is active, another viable integration is to define a policy that
110-
defaults to bind-by-reference. This policy could be added to `Bind.h`.
134+
statement is active, you may want a policy that defaults to bind-by-reference.
135+
For this usage, take a look at `AsArgument.h`.
111136

112137
### To integrate `Bind.h`, take `bind::args` via CTAD in an immovable class
113138

@@ -119,7 +144,7 @@ void foo(bind::args<T> args) {
119144
// Read "Using your bound args" for how to access `args`
120145
}
121146
// User code
122-
foo(bind::args{5, constant(bind::in_place<Bar>(x), std::move(y))});
147+
foo(bind::args{5, bind::constant(bind::in_place<Bar>(x), std::move(y))});
123148
```
124149
125150
#### Alternative: API with no user-visible `bind::args`
@@ -129,7 +154,7 @@ Before going down this road, read this section *carefully*. Needing to depend
129154
on CTAD limits your template deduction capabilities, and forces you to
130155
implement each API method as a class.
131156
132-
In `Bind.h`, all the modifiers (like `constant`) look similar to this:
157+
In `Bind.h`, all the modifiers (like `bind::constant`) look similar to this:
133158
134159
```cpp
135160
template <typename... Ts>
@@ -158,39 +183,39 @@ example, see the `#if 0` in the test under `all_tests_run_at_build_time`.
158183

159184
Notes:
160185
- `private` inheritance prevents `YOUR_TYPE` from being nested inside
161-
binding modifiers like `constant()`. You can relax to `public`, but
162-
only if your type has the inherited ctor shown above, and can logically
163-
be thought of as a bag of args.
186+
binding modifiers like `bind::constant()`. You can relax to `public`, but
187+
only if your type has the inherited ctor shown above, and can logically be
188+
thought of as a bag of args.
164189
- A linter is proposed, but not yet implemented, for detecting cases
165190
where `Bind.h` helpers are being taken by-reference. With this linter, the
166191
prvalue-only protection against lifetime bugs will be robust.
167192

168-
(2) Prvalue semantics only allows us to take arguments by-value. C++20
169-
lacks perfect forwarding for prvalues, and there is not even an accepted
170-
proposal for future releases (though P2785 would help). Yet, in generic
171-
code, we want to allow packs of arguments where some are binding helpers
172-
(like `constant(5)`) and others are perfect-forwarded references (like `x`).
193+
(2) Prvalue semantics only allows us to take arguments by-value. C++20 lacks
194+
perfect forwarding for prvalues, and there is not even an accepted proposal for
195+
future releases (though P2785 would help). Yet, in generic code, we want to
196+
allow packs of arguments where some are binding helpers (like
197+
`bind::constant(5)`) and others are perfect-forwarded references (like `x`).
173198
Without prvalue perfect forwarding, the next best trick is to implicitly
174199
convert every arg to a `bind::args<T>` value, as done by this ctor.
175200
- If the argument type `T` derives from `bind::ext::like_args`, we wrap it in
176201
`struct args<T> : T`, moving the underlying data via the
177202
implementation-detail `unsafe_move_args` protocol. This handles the case
178-
when the user passes a modifier like `constant(5)` as an arg.
203+
when the user passes a modifier like `bind::constant(5)` as an arg.
179204
- For all other `T`, `args<T>` simply captures a forwarding
180205
reference to the input.
181206

182207
IMPORTANT:
183-
- To be lifetime-safe, this ctor takes the `args<Ts>` by-value.
184-
- Do **NOT** assume that `Ts` are your input types. A single `args`
185-
may represent a sequence of arguments to bind (e.g. `constant(5, x)`,
186-
and the base class `args<Ts...>` takes care of flattening these
187-
for you. The usage is explained below.
208+
- To be lifetime-safe, this ctor takes the `bind::args<Ts>` by-value.
209+
- Do **NOT** assume that `Ts` are your input types. A single `bind::args`
210+
may represent a sequence of arguments to bind -- `bind::constant(5, x)` --
211+
and the base class `bind::args<Ts...>` takes care of flattening these for
212+
you. The usage is explained below.
188213

189214
(3) The deduction guide is necessary so that the class's ctor (2) can
190-
implicitly convert each argument into a `args<T>` value. In other
215+
implicitly convert each argument into a `bind::args<T>` value. In other
191216
words, it arranges for `T` to record the type of the argument as provided.
192217

193-
Once the base `bound_arg<Ts...>` is constructed, you can access the
218+
Once the base `bind::args<Ts...>` is constructed, you can access the
194219
**flattened** bound args:
195220
- The "input" and "storage" types for each argument via the class template
196221
`bind_to_storage_policy`, and member type list `binding_list_t`.
@@ -216,7 +241,7 @@ There are two natural choices for **when** to run your business logic:
216241
recommendable pattern, since error handling gets trickier (either your
217242
class models both value & error states, or your ctor throws). However,
218243
it can give a simpler UX. Implementing this requires some adjustments:
219-
- Stop inheriting from `args`, since you would no longer want to
244+
- Stop inheriting from `bind::args`, since you would no longer want to
220245
store the bound args tuple, but only the API's result.
221246
- Instead, lightly refactor `Bind.h` to expose the argument-flattening
222247
logic by composition.

0 commit comments

Comments
 (0)