-
Notifications
You must be signed in to change notification settings - Fork 260
How do you forward non-last-use argument? #77
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
Comments
The grammar already allows for |
What is the precedence on those? I think |
Ah, that does not work then. These call annotations are soft keywords of the call site ( |
I don't think you can use |
Why support forward at all? I thought |
Same reason it exists in C++, so you don't have to write the same function twice (for each forwarded parameter). |
I may be missing something.
|
|
Not according to the talk Herb gave. When there's a definite last use of a variable, it's passed as r-value as a copy argument. Watch at 1:27. |
That's not argument deduction, that's just changing the value category of an argument. Anyways, definite last use is not a sufficient treatment for these semantics, as this ticket shows. |
So by argument deduction you also mean deducing constness and whether it's a ref or a value?
It could be, if we change the granularity to fields instead of whole objects. |
Correct me if I'm wrong, but I don't think fine-grain semantics for forward parameters makes sense, depending on the design goals of the language. Let me clarify: According to cpp core guideline F.19, any forward parameter object used more than or less than exactly once in any static code path should be brought to the attention of the human in charge. (these usages should be "flag[ged]" as they put it) If we are to enforce guideline F.19, it seems like using the
This feels like we're over-saturating forward parameter annotations semantics with more than what they are intended to be used for (i.e. we are putting single object forwarding and aggregate subobjects forwarding in the same bucket). Unless we resort to verifying that every subobject of a forward parameter (xor the parameter object itself) is used exactly once on every static code path to ensure mutually exclusive subobject forwarding, I think using a forward parameter annotation in this way should be a compile-time error. And even if we implemented it like that, it seems to me that using fine-grain semantics for forward parameter objects in this way would make the language harder to teach and reason about. Looking into how I don't know how frequently aggregate subobjects forwarding should be used in c++ for any purpose, or whether there are better alternatives in general (for example, by calling standard functions that hide the use of such forwarding instead of defining your own). If this kind of forwarding is a narrow feature for users in general, then (in light of how std::tuple carries the value category for each of its element types) I'd be inclined to prefer being able to wrap aggregate subobjects into something like a The point of namespace cpp2 {
template<typename A>
class forward {
A _a;
public:
forward(auto&& a):
_a(std::forward<A>(a))
{}
... // (deleted) constructors and operator= omitted
~forward() {
... // ensure parameter was consumed exactly once here?
// iiuc this can and should be done at compile time (not necessarily implemented here)
}
A&& get() {
return std::forward<A>(_a);
}
... // more members if needed
};
forward(auto&& a) -> forward<decltype(a)>;
} And then: #include <cpp2util.h>
#include <string>
void g(auto&& x);
struct pair_t {
cpp2::forward<std::string> x, y;
};
f: (pair : pair_t) = {
g(pair.x); // forwards
g(pair.y); // and forwards too!
g(pair); // forwards as a copy in this case (pair is implicitly annotated with "in")
// if we are to enforce guideline F.19, this code should not be allowed
// to compile. Ideally, the constructor of `cpp2::forward` doesn't allow
// an object to be forwarded more than once. Since `pair` is an in parameter,
// it should use the copy constructors of `x` and `y`, which should in turn
// attempt to forward. Since both `x` and `y` have already been forwarded,
// according to guideline F.19, this should result in a compile-time error
} My intention here is that the code generator would take care of calling As a side note, of course my point of view is highly dependent on whether guideline F.19 is intended to be strictly enforced at compile time. |
I think @ljleb said it all. I don't currently aim to allow individual forwarding of members in separate non-fold expressions, so I'll close this. If there is demand for it we can always revisit it in the future. Thanks! |
@ljleb is wrong about how tuple works and is misreading cpp core guideline F.19 (which is also broken). You have to forward the function parameter, not a subobject of the function parameter, using the forwarding template parameter. The template <typename _Fn, typename _Tuple, size_t... _Idx>
constexpr decltype(auto)
__apply_impl(_Fn&& __f, _Tuple&& __t, index_sequence<_Idx...>)
{
return std::__invoke(std::forward<_Fn>(__f),
std::get<_Idx>(std::forward<_Tuple>(__t))...);
} The parameter https://godbolt.org/z/d46n8EMrs apply: (forward t: std::tuple<int, double>) = {
discard(t.x);
discard(t.y);
}
The forward language in D0708, which indicates definite last use, is not correct. |
@seanbaxter Thanks for the notes! Some thoughts: For the C++ Core Guidelines: We'd love to know if F.19 is broken... can you please open an issue in the Guidelines repo to tell us how (and if possible suggest an improvement)? Thanks! For paper d0708: I'd like to fix bugs in d0708... if the explanation of my intent below doesn't address your concerns, please let me know the forward language that you think isn't correct, and how the example is broken. Thanks! For Cpp2/cppfront:
The second function is new functionality that needs this commit, it wouldn't compile before. See the new regression test Bug reports and constructive feedback are always welcome! Thanks. |
BTW, I guess your final comment turned this from a Your question at top was:
The above is still the default. To opt into to forwarding both pieces, the answer in this commit would be to
At least that's the direction I had in mind, that this commit starts to explore. Thanks for the example! |
Now it's broken in a different way. 41673ef translates: apply_explicit_forward: (forward t: std::pair<X, X>) = {
discard(forward t.first);
discard(forward t.second);
} into auto apply_explicit_forward(auto&& t) -> void
requires std::is_same_v<CPP2_TYPEOF(t), std::pair<X,X>>
#line 19 "mixed-forwarding.cpp2"
{ discard(CPP2_FORWARD(t.first));
discard(CPP2_FORWARD(CPP2_FORWARD(t).second));
} apply_explicit_forward is broken when you pass an lvalue, because that gets forwarded to discard as an xvalue. This is an illegal use of decltype--CPP2_FOWARD(t.first) will strip the reference and The |
An alternative to precedence is the forwarding operator, |
Agreed, commit c39a94c should address this. Now it translates
to
See also the commit comments re grammar. I'm currently running with @JohelEGP True, and Barry is a source of good ideas. For Cpp2 though, one of my stakes in the ground is that declaration syntax is consistent with use syntax, so for Cpp2 I'd want to change the syntax in both places (declaration and use) or neither rather than introduce an asymmetry there. |
How do you forward a non-last-use argument? It has to be done when destructuring aggregates. std::apply does this variadically.
The text was updated successfully, but these errors were encountered: