-
Notifications
You must be signed in to change notification settings - Fork 260
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
[BUG] forward return passing style produces bad cpp1 return type #248
Comments
The issue found during discussion here: #247 |
This is offtopic, but copy passing convention seems... out of place. It does not convey intent like the other 5 conventions do and I think copy should be an annotation at call site rather than in the function signature. It should be made so that user calls method/function if he wants to pass the copy of an object to function. Something like, |
[[nodiscard]] auto fun4(cpp2::in<int> i) -> int&{ // should be `const int&`
return i;
}
[[nodiscard]] auto fun4_in(cpp2::in<int> i) -> int&{ // should be `const int&`
return i;
} Are these functions intentionally returning references to local variables? Shouldn't they just be |
@AbhinavK00, there are other issues regarding passing style (see: #231 (comment)) @hsutter mentioned the solution here: #198 (comment)
If I understand correctly, the idea is to emphasize intention on the call side by adding a passing style keyword: // what is supported at the moment
fun(out x); // x will be initialized (modified before use) in the fun
fun(move x); // x moved to fun
fun(forward x); // x forward to fun
// what is planned
fun(copy x); // x copied to fun
fun(inout x); // x passed as inout parameter and cannot be rvalue |
@filipsajdak I see, what I was saying is that I don't think there is a need for a copy passing convention, we already have five. We can always have user pass a copy by calling a method/function and take it in as a move parameter.
You should check out full post for more info: |
@leejy12 I missed your comment. Currently, the cppfront is returning a reference to temporary, which is an error (it will not compile on the cpp1 compiler). Returning We can do the proper thing for generic function as the following: fun: (i : int) -> forward _ = {
return i;
} It will generate: [[nodiscard]] auto fun(cpp2::in<int> i) -> decltype(auto){
return i;
} And the return type will be deduced correctly. There is an issue when we want to specify the forward return type, e.g. fun3_forward: (forward i : int) -> forward _ = {
return i;
} Generates: [[nodiscard]] auto fun3_forward(auto&& i) -> decltype(auto) // template or auto is needed to allow perfect forwarding
requires std::is_same_v<CPP2_TYPEOF(i), int> // requires limits its match to only int
{
return CPP2_FORWARD(i);
}
|
Thanks. The purpose of |
@hsutter Take a look at the rest test cases I have made - maybe it will bring you more cases. fun1: (i) -> forward _ = {
return i;
}
fun2: (i : _) -> forward _ = {
return i;
}
fun3: (i : int) -> forward _ = {
return i;
}
fun4: (i : int) -> forward int = {
return i;
}
fun1_in: (in i) -> forward _ = {
return i;
}
fun2_in: (in i : _) -> forward _ = {
return i;
}
fun3_in: (in i : int) -> forward _ = {
return i;
}
fun4_in: (in i : int) -> forward int = {
return i;
}
fun1_inout: (inout i) -> forward _ = {
return i;
}
fun2_inout: (inout i : _) -> forward _ = {
return i;
}
fun3_inout: (inout i : int) -> forward _ = {
return i;
}
fun4_inout: (inout i : int) -> forward int = {
return i;
}
fun1_copy: (copy i) -> forward _ = {
return i;
}
fun2_copy: (copy i : _) -> forward _ = {
return i;
}
fun3_copy: (copy i : int) -> forward _ = {
return i;
}
fun4_copy: (copy i : int) -> forward int = {
return i;
}
fun1_move: (move i) -> forward _ = {
return i;
}
fun2_move: (move i : _) -> forward _ = {
return i;
}
fun3_move: (move i : int) -> forward _ = {
return i;
}
fun4_move: (move i : int) -> forward int = {
return i;
}
fun1_forward: (forward i) -> forward _ = {
return i;
}
fun2_forward: (forward i : _) -> forward _ = {
return i;
}
fun3_forward: (forward i : int) -> forward _ = {
return i;
}
fun4_forward: (forward i : int) -> forward int = {
return i;
}
// fun1_out: (out i) -> forward _ = { // error: (temporary alpha limitation) an 'out' parameter cannot be a deduced type (at ')')
// return i;
// }
// fun2_out: (out i : _) -> forward _ = { // error: (temporary alpha limitation) an 'out' parameter cannot be a deduced type (at ')')
// return i;
// }
fun3_out: (out i : int) -> forward _ = {
i = 42;
return i;
}
fun4_out: (out i : int) -> forward int = {
return fun4_out_int(out i);
}
fun4_out_int: (out i : int) -> forward int = {
i = 42;
return i;
} Generates: [[nodiscard]] auto fun1(auto const& i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun2(auto const& i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun3(cpp2::in<int> i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun4(cpp2::in<int> i) -> int&{
return i;
}
[[nodiscard]] auto fun1_in(auto const& i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun2_in(auto const& i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun3_in(cpp2::in<int> i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun4_in(cpp2::in<int> i) -> int&{
return i;
}
[[nodiscard]] auto fun1_inout(auto& i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun2_inout(auto& i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun3_inout(int& i) -> decltype(auto){
return i;
}
[[nodiscard]] auto fun4_inout(int& i) -> int&{
return i;
}
[[nodiscard]] auto fun1_copy(auto i) -> decltype(auto){
return std::move(i);
}
[[nodiscard]] auto fun2_copy(auto i) -> decltype(auto){
return std::move(i);
}
[[nodiscard]] auto fun3_copy(int i) -> decltype(auto){
return std::move(i);
}
[[nodiscard]] auto fun4_copy(int i) -> int&{
return std::move(i);
}
[[nodiscard]] auto fun1_move(auto&& i) -> decltype(auto){
return std::move(i);
}
[[nodiscard]] auto fun2_move(auto&& i) -> decltype(auto){
return std::move(i);
}
[[nodiscard]] auto fun3_move(int&& i) -> decltype(auto){
return std::move(i);
}
[[nodiscard]] auto fun4_move(int&& i) -> int&{
return std::move(i);
}
[[nodiscard]] auto fun1_forward(auto&& i) -> decltype(auto){
return CPP2_FORWARD(i);
}
[[nodiscard]] auto fun2_forward(auto&& i) -> decltype(auto){
return CPP2_FORWARD(i);
}
[[nodiscard]] auto fun3_forward(auto&& i) -> decltype(auto)
requires std::is_same_v<CPP2_TYPEOF(i), int>
#line 90 "tests/bug_forward_return_type.cpp2"
{ return CPP2_FORWARD(i);
}
[[nodiscard]] auto fun4_forward(auto&& i) -> int&
requires std::is_same_v<CPP2_TYPEOF(i), int>
#line 94 "tests/bug_forward_return_type.cpp2"
{ return CPP2_FORWARD(i);
}
// fun1_out: (out i) -> forward _ = { // error: (temporary alpha limitation) an 'out' parameter cannot be a deduced type (at ')')
// return i;
// }
// fun2_out: (out i : _) -> forward _ = { // error: (temporary alpha limitation) an 'out' parameter cannot be a deduced type (at ')')
// return i;
// }
[[nodiscard]] auto fun3_out(cpp2::out<int> i) -> decltype(auto){
i.construct(42);
return i.value();
}
[[nodiscard]] auto fun4_out(cpp2::out<int> i) -> int&{
return fun4_out_int(& i);
}
[[nodiscard]] auto fun4_out_int(cpp2::out<int> i) -> int&{
i.construct(42);
return i.value();
} |
Thanks for the answer.
Cool. A cpp2 implementation could probably look something like this:
which will transpile to export template <typename T>
class vector {
private:
T* data;
public:
T& operator[](size_type pos) {
return (data + pos)*;
}
const T& operator[](size_type pos) const {
return (data + pos)*;
}
}; |
Quick ack, since this was in my inbox this morning and I have a minute over morning coffee jot down notes for myself:
Briefly:
I think so. Examples of its usefulness today would be all functions that take a forwarding reference parameter and then return it with a
Pretty close, the syntax I'm contemplating is more like this which avoids special class/member grammar (quick notes: data members private by default and functions public by default, and explicit
... something like that.
Pretty much, but also with automatic bounds checking by default so you'll see the body emitted as |
@hsutter What about the following code print: (inout o : std::ostream, x : sometype) -> forward std::ostream = {
return o << "# (x.name)$\n";
} The intention is to return a reference to allow chaining: o.print(x1).print(x2).print(x3); In the current cppfront implementation, the code mentioned generates the following cpp1 code: [[nodiscard]] auto print(std::ostream& o, cpp2::in<sometype> x) -> std::ostream&{
return o << "# " + cpp2::to_string(x.name) + "\n";
}
Does it mean that my code is illegal? Maybe there is other way to achieve that? |
@filipsajdak, yes this is the kind of case I meant with the later addition that " |
Edit: #250 shows my point. Edit 2: Maybe the template parameters can default to typename so we can write |
Re the earlier problem of returning a local via a |
I think the main issues in this thread have been addressed now, so I'll close it. If I missed something, please reopen and point it out! Thanks again. |
@hsutter The following cases: fun3: (i : int) -> forward _ = {
return i;
}
fun4: (i : int) -> forward int = {
return i;
}
fun3_in: (in i : int) -> forward _ = {
return i;
}
fun4_in: (in i : int) -> forward int = {
return i;
} Generate: [[nodiscard]] auto fun3(cpp2::in<int> i) -> auto&&{
return i;
}
[[nodiscard]] auto fun4(cpp2::in<int> i) -> int&{
return i;
}
[[nodiscard]] auto fun3_in(cpp2::in<int> i) -> auto&&{
return i;
}
[[nodiscard]] auto fun4_in(cpp2::in<int> i) -> int&{
return i;
} All return reference to the local variable. |
@filipsajdak Catching up on this last comment, I've now pushed a commit that disallows forward-return of an |
Why I think the right diagnosis isn't to ban forward return of |
@AbhinavK00 scalars are passed by value when used |
See the wiki documentation page: https://github.com/hsutter/cppfront/wiki/Cpp2:-operator=,-this-&-that See also the new `pure2-types-smf*.cpp2` test cases for examples. Made Cpp1 assignment "return *this;", closes #277 Changed in<T> to pass fundamental types by value. Only those are known to be always complete. Partly addresses #270. Also partly improves #282.
Like today's C++1, with |
Why Edit: @filipsajdak , I know they're passed by value but we can do an exception for when the function forward returns it. |
So you want to write a concrete I quickly changed |
Not really, my point is that the current fix disallows writing the templated function even though it's fully correct. Edit: The solution descirbed by you using |
That was a mistake. My intent was to reimplement |
I think using the above rule and templates is the correct way to implement min: <T> (a: T, b: T) -> forward T = {
if a < b {
return a;
}
else {
return b;
}
} Since there's no way of explicitly defining a parameter to be const reference in Cpp2, I think some special rules are needed for Also, I wonder if we could improve the semantics of |
Just like #250 (comment), this no longer works: https://godbolt.org/z/EGPYevc16. |
…r#248 comment Addresses hsutter#248 (comment)
In the current implementation of cppfront, we can use a forward return passing style to return a reference from a function or use
decltype(auto)
to return an exact cv-qualified type.Unfortunately, in some cases, it produces the wrong cpp1 code. E.g., in the following code:
Generates (skipping boilerplate):
The text was updated successfully, but these errors were encountered: