https://gcc.gnu.org/g:7ae49df44393ea4ce5024511a3d1b2ba81deca90

commit r17-5007-g7ae49df44393ea4ce5024511a3d1b2ba81deca90
Author: Marek Polacek <[email protected]>
Date:   Wed Oct 7 15:56:28 2026 -0400

    c++: TARGET_EXPR in assignment [PR127710]
    
    Here we crash on
    
      auto foo = [] {} = {};
    
    on the assert in build_over_call:
    
      gcc_assert (TREE_CODE (arg) != TARGET_EXPR
                  || force_elide
                  /* It's from binding the ref parm to a packed field. */
                  || convs[0]->need_temporary_p
                  || ... )
    
    which makes sure that we are initializing directly from class prvalues
    rather than copying them.  The problem is that we created
    *&TARGET_EXPR <D.2704, {}> in
    
          if (is_really_empty_class (type, /*ignore_vptr*/true))
            {
              /* Avoid copying empty classes, but ensure op= returns an lvalue 
even
                 if the object argument isn't one.  */
              to = force_lvalue (to, complain);
              val = build2 (COMPOUND_EXPR, type, arg, to);
              suppress_warning (val, OPT_Wunused);
            }
    
    where we want an lvalue, but then in cp_build_addr_expr_1 (Let &* cancel out
    to simplify resulting code) we get &TARGET_EXPR and then build_over_call
    strips the & and we end up with a prvalue.
    
    The test is actually ([]{}).operator=({}) where binding the []{}
    object materializes a temporary, operator= returns an lvalue and foo is
    copy-initialized from that lvalue.
    
    My fix is to follow what cp_build_modify_expr (and other places) do:
    build up a COMPOUND_EXPR and use the TARGET_EXPR_SLOT so that the
    TARGET_EXPR can't become a prvalue.
    
            PR c++/127710
    
    gcc/cp/ChangeLog:
    
            * cvt.cc (force_lvalue): For a TARGET_EXPR, build
            (TARGET_EXPR<...>, slot).
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp2a/class-copy1.C: New test.
            * g++.dg/cpp2a/lambda-assign1.C: New test.
    
    Reviewed-by: Jason Merrill <[email protected]>

Diff:
---
 gcc/cp/cvt.cc                               | 10 +++++++++-
 gcc/testsuite/g++.dg/cpp2a/class-copy1.C    | 29 +++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/cpp2a/lambda-assign1.C | 14 ++++++++++++++
 3 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc
index ff54647c5b10..845719c20438 100644
--- a/gcc/cp/cvt.cc
+++ b/gcc/cp/cvt.cc
@@ -580,7 +580,15 @@ force_rvalue (tree expr, tsubst_flags_t complain)
 tree
 force_lvalue (tree expr, tsubst_flags_t complain)
 {
-  if (!lvalue_p (expr))
+  if (lvalue_p (expr))
+    return expr;
+
+  /* If we build up *&TARGET_EXPR, we have an lvalue, but it could be
+     folded back to a prvalue by removing the *&.  */
+  if (TREE_CODE (expr) == TARGET_EXPR)
+    expr = build2 (COMPOUND_EXPR, TREE_TYPE (expr), expr,
+                  TARGET_EXPR_SLOT (expr));
+  else
     {
       expr = cp_build_addr_expr (expr, complain);
       expr = cp_build_indirect_ref (input_location, expr, RO_ARROW, complain);
diff --git a/gcc/testsuite/g++.dg/cpp2a/class-copy1.C 
b/gcc/testsuite/g++.dg/cpp2a/class-copy1.C
new file mode 100644
index 000000000000..95d7e028f745
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/class-copy1.C
@@ -0,0 +1,29 @@
+// PR c++/127710
+// { dg-do run { target c++20 } }
+
+struct S { int i; };
+struct A {
+  A() = default;
+  A(int i, char c) : i(i), c(c) {}
+  int i;
+  char c;
+};
+struct B : A { };
+// Try an empty closure as a base.
+struct W : decltype([] {}) { int i; };
+
+int
+main ()
+{
+  S s = S{} = S{1};
+  if (s.i != 1)
+    __builtin_abort ();
+
+  B b = B{} = B{{2, 'x'}};
+  if (b.i != 2 || b.c != 'x')
+    __builtin_abort ();
+
+  W w = W{} = W{{}, 3};
+  if (w.i != 3)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-assign1.C 
b/gcc/testsuite/g++.dg/cpp2a/lambda-assign1.C
new file mode 100644
index 000000000000..007219a3ba48
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/lambda-assign1.C
@@ -0,0 +1,14 @@
+// PR c++/127710
+// { dg-do compile { target c++20 } }
+
+auto l1 = [] {} = {};
+void
+g ()
+{
+  auto l2 = [] {} = {};
+  // [expr.prim.lambda.closure]/17: The closure type associated with
+  // a lambda-expression [...] has a deleted copy assignment operator
+  // if the lambda-expression has a lambda-capture.
+  int i = 9;
+  auto l3 = [i] {} = {};  // { dg-error "use of deleted function" }
+}

Reply via email to