-
Notifications
You must be signed in to change notification settings - Fork 260
[QUESTION] How to properly capture this
pointer in lambda
#281
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
Thanks, I'll take a look. I thought the Also: This example found a bug in the code I just pushed, because it has a lambda inside an |
Interim update: I can't repro your full example because I don't have all the types, but here's what I know so far... Capturing a pointer to
The problem seems to be when a member function has a lambda that does a UFCS call to a member function (that's a mouthful), because that causes the member function name to appear unadorned. In this example the lambda contains the expression Here's a distilled repro:
That's what I have so far... |
(Again, the answer to your original question "how to capture I think I've found the problem that your code hit, and it seems to be a bug in Clang and MSVC. What's happening is that Clang and MSVC are not properly discarding the not-taken
I don't have access to your entire test case, but I'm about to push a commit that I've verified does fix a slightly-distilled version of your test case. @filipsajdak please check to see whether it fixes your whole test case. Thanks again for reporting, this was a subtle issue where (if I'm not mistaken) two of the three major commercial C++ compilers had the same bug! That doesn't happen every day. |
… and MSVC bug, addresses #281 Also fix the logic in the `is_constructor_*` and `is_assignment_*` functions
Hi @hsutter, Thank you for working on that issue, and I am sorry I did not help with it so far - I am stuck with other work. I have been investigating this issue. I have found at least one issue (that still manifests in the current main (de25e0e). Also, I have simplified the code not to contain unknown types: element: type = {
children: std::vector<std::variant<std::unique_ptr<element>>> = ();
parent: *element;
get_children: (this) -> forward _ = children;
get_children: (inout this) -> forward _ = children;
get_parent: (this) -> forward _ = parent;
get_parent: (inout this) -> forward _ = parent;
set_parent: (inout this, p : *element) = parent = p;
operator=: (out this, forward t : _ ) = {
parent = nullptr;
}
operator=: (out this, forward cs : std::vector<std::unique_ptr<element>>) = {
children = cs;
parent = nullptr;
for children do :(inout c) = {
c*.set_parent(this&); // case 1: cpp2 works, cpp1 fail
}
}
operator=: (out this, forward cs : std::vector<std::variant<std::unique_ptr<element>>>) = {
children = cs;
parent = nullptr;
for children do :(inout v) = {
std::visit(:(inout c) = {
c*.set_parent(this&$); // case 2: cpp2 fail (ICE)
}, v);
}
}
} There are two cases here. Case 1 is the original one. It fails even without using Case 2 causes ICE in cppfront - it looks like diff --git a/source/cppfront.cpp b/source/cppfront.cpp
index c1744eb..14b43f7 100644
--- a/source/cppfront.cpp
+++ b/source/cppfront.cpp
@@ -2009,8 +2009,10 @@ public:
for (auto& cap : captures.members)
{
assert(cap.capture_expr->cap_grp == &captures);
+ cap.str.clear();
print_to_string(&cap.str, *cap.capture_expr, true);
suppress_move_from_last_use = true;
+ cap.str_suppressed_move.clear();
print_to_string(&cap.str_suppressed_move, *cap.capture_expr, true);
suppress_move_from_last_use = false;
} Case 2 still fail on cpp1 side. |
@hsutter I am checking your latest change. |
@hsutter what I can quickly check is that the following code: element: type = {
children: std::vector<std::variant<std::unique_ptr<element>>> = ();
parent: *element;
get_children: (this) -> forward _ = children;
get_children: (inout this) -> forward _ = children;
get_parent: (this) -> forward _ = parent;
get_parent: (inout this) -> forward _ = parent;
set_parent: (inout this, p : *element) = parent = p;
operator=: (out this, forward t : _ ) = {
parent = nullptr;
}
operator=: (out this, forward cs : std::vector<std::variant<std::unique_ptr<element>>>) = {
children = cs;
parent = nullptr;
for children do :(inout v) = {
std::visit(:(inout c) = {
c*.set_parent(this&$); // cpp2 fail (ICE)
}, v);
}
}
} with the patch: diff --git a/source/cppfront.cpp b/source/cppfront.cpp
index c1744eb..14b43f7 100644
--- a/source/cppfront.cpp
+++ b/source/cppfront.cpp
@@ -2009,8 +2009,10 @@ public:
for (auto& cap : captures.members)
{
assert(cap.capture_expr->cap_grp == &captures);
+ cap.str.clear();
print_to_string(&cap.str, *cap.capture_expr, true);
suppress_move_from_last_use = true;
+ cap.str_suppressed_move.clear();
print_to_string(&cap.str_suppressed_move, *cap.capture_expr, true);
suppress_move_from_last_use = false;
} Generates: #define CPP2_USE_MODULES Yes
#include "cpp2util.h"
#line 1 "../tests/capture_this.cpp2"
class element;
//=== Cpp2 definitions ==========================================================
#line 1 "../tests/capture_this.cpp2"
class element {
private: std::vector<std::variant<std::unique_ptr<element>>> children {};
private: element* parent;
public: [[nodiscard]] auto get_children() const -> auto&& { return children; }
public: [[nodiscard]] auto get_children() -> auto&& { return children; }
public: [[nodiscard]] auto get_parent() const -> auto&& { return parent; }
public: [[nodiscard]] auto get_parent() -> auto&& { return parent; }
public: auto set_parent(cpp2::in<element*> p) -> void { parent = p; }
public: explicit element(auto&& t)
: parent{ nullptr }
#line 13 "../tests/capture_this.cpp2"
{
}
#line 12 "../tests/capture_this.cpp2"
public: auto operator=(auto&& t) -> element& {
children = ;
parent = nullptr;
#line 13 "../tests/capture_this.cpp2"
return *this;
#line 14 "../tests/capture_this.cpp2"
}
public: explicit element(auto&& cs)
requires std::is_same_v<CPP2_TYPEOF(cs), std::vector<std::variant<std::unique_ptr<element>>>>
#line 17 "../tests/capture_this.cpp2"
: children{ CPP2_FORWARD(cs) }
, parent{ nullptr }
#line 18 "../tests/capture_this.cpp2"
{
for ( auto&& cpp2_range = children; auto& v : cpp2_range ) {
std::visit([&, _1 = (&(*this))](auto& c) -> void{
CPP2_UFCS(set_parent, (*cpp2::assert_not_null(c)), _1);// cpp2 fail (ICE)
}, v);
}
}
#line 16 "../tests/capture_this.cpp2"
public: auto operator=(auto&& cs) -> element&
requires std::is_same_v<CPP2_TYPEOF(cs), std::vector<std::variant<std::unique_ptr<element>>>>
#line 17 "../tests/capture_this.cpp2"
{
children = CPP2_FORWARD(cs);
parent = nullptr;
#line 18 "../tests/capture_this.cpp2"
for ( auto&& cpp2_range = children; auto& v : cpp2_range ) {
std::visit([&, _1 = (&(*this))](auto& c) -> void{
CPP2_UFCS(set_parent, (*cpp2::assert_not_null(c)), _1);
}, v);
}
return *this;
#line 24 "../tests/capture_this.cpp2"
}
};
It contains an issue in the implementation of public: auto operator=(auto&& t) -> element& {
children = ; // <<<< nothing is assigned to children
parent = nullptr;
#line 13 "../tests/capture_this.cpp2"
return *this;
#line 14 "../tests/capture_this.cpp2"
} After correcting it manually, it compiles fine. That is all I can do now - running original test cases requires me to do the rebase of the branch with raw string literals, which I will do later. Thank you again for investigating it, and sorry for not helping more with it. |
I have rebased my other branch, and I am not getting the previous errors, but I have plenty of new ones: I will investigate and get back to you with more content - it may be in the new issue. public: explicit spec(cpp2::in<std::string> n)
: name{ n }
#line 27 "/Users/filipsajdak/dev/execspec/libs/execspec/include/execspec/spec.h2"
{
}
#line 26 "/Users/filipsajdak/dev/execspec/libs/execspec/include/execspec/spec.h2"
public: auto operator=(cpp2::in<std::string> n) -> spec&
, description{ "" }
, config{ }
, stages{ }
#line 27 "/Users/filipsajdak/dev/execspec/libs/execspec/include/execspec/spec.h2"
{
name = n;
#line 28 "/Users/filipsajdak/dev/execspec/libs/execspec/include/execspec/spec.h2"
return *this;
#line 29 "/Users/filipsajdak/dev/execspec/libs/execspec/include/execspec/spec.h2"
} I see that there is an issue with generating: public: auto operator=(cpp2::in<std::string> n) -> spec&
, description{ "" }
, config{ }
, stages{ } |
@hsutter as |
This example now works: main: () = { v: std::vector = ( 1, 2, 3, 4, 5 ); // Definite last use of v => move-capture v into f's closure f := :() -> forward _ = v$; // Now we can access the vector captured inside f()... f().push_back(6); for f() do(e) std::cout << e; // prints 123456 }
… and MSVC bug, addresses hsutter#281 Also fix the logic in the `is_constructor_*` and `is_assignment_*` functions
Is there an MSVC bug report for #281 (comment)? |
In the current implementation of cppfront (4c52d2d) I am trying to capture
this
pointer to lambda:that produces (skipping boilerplate_:
and fail to compile with an error:
When capturing value before lambda creation like the following:
Generates:
That also ends with an error:
When trying another syntax:
That worked and produced:
Unfortunately, this solution makes a copy of
*this
and calls set_parent with the address to the copy.Question
Could you tell me how to capture a pointer to
this
in lambda?The text was updated successfully, but these errors were encountered: