Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
-- >8 --
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.
---
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(-)
create mode 100644 gcc/testsuite/g++.dg/cpp2a/class-copy1.C
create mode 100644 gcc/testsuite/g++.dg/cpp2a/lambda-assign1.C
diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc
index ff54647c5b1..845719c2043 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 00000000000..95d7e028f74
--- /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 00000000000..007219a3ba4
--- /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" }
+}
base-commit: fb8a2387e35a92e5339df5f7dd434ad14f043d09
--
2.56.0