https://gcc.gnu.org/g:826a52902ecef7cdda3bfe1e1cf10cd8eb49563b

commit r17-4187-g826a52902ecef7cdda3bfe1e1cf10cd8eb49563b
Author: Jakub Jelinek <[email protected]>
Date:   Sat Sep 12 09:56:25 2026 +0200

    c++: Implement C++26 CWG 3158 - const-ification of Splice Expressions 
[PR125840]
    
    The paper claims that GCC already implements it, which is only partially
    the case.  Non-dependent splice expression of a variable (of function
    parameter or real variable) inside of contract conditions goes through
    finish_id_expression -> finish_id_expression_1 which calls
    constify_contract_access when needed.
    We don't call it during tsubst_splice_expr though (and the paper requires
    us to constify in that case), and on the other side, for splice expression
    of a reflection of an object we weren't distinguishing it from splice
    expression of a reflection of a variable.
    
    So, the following patch
    1) for splice wraps decls from REFLECT_OBJECT into a VCE, so that they
       still act as lvalues, but aren't considered the variables themselves,
       just an lvalue referencing them
    2) calls constify_contract_access during tsubst_splice_expr
    
    I've added all testcases from the paper (after fixing numerous bugs in
    those), plus related stuff I could come up with.
    
    2026-09-12  Jakub Jelinek  <[email protected]>
    
            P3598R0
            PR c++/125840
            * reflect.cc: Implement C++26 CWG 3158 - const-ification of Splice
            Expressions.
            (splice): Remember REFLECT_EXPR_KIND of REFLECT_EXPR.  Set refl to
            REFLECT_EXPR_HANDLE earlier.  For REFLECT_OBJECT wrap decls into
            a VCE to differentiate it from corresponding variables.
            * pt.cc (tsubst_splice_expr): Call constify_contract_access if
            needed.
    
            * g++.dg/reflect/contract1.C: New test.
            * g++.dg/reflect/contract2.C: New test.
            * g++.dg/reflect/contract3.C: New test.
            * g++.dg/reflect/contract4.C: New test.
            * g++.dg/reflect/contract5.C: New test.
            * g++.dg/reflect/reflect_object2.C (g): Add test for setting of
            splice expr of an object reflection.
    
    Reviewed-by: Jason Merrill <[email protected]>

Diff:
---
 gcc/cp/pt.cc                                   |  6 ++-
 gcc/cp/reflect.cc                              | 10 +++-
 gcc/testsuite/g++.dg/reflect/contract1.C       | 32 ++++++++++++
 gcc/testsuite/g++.dg/reflect/contract2.C       | 71 ++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/reflect/contract3.C       | 40 +++++++++++++++
 gcc/testsuite/g++.dg/reflect/contract4.C       | 39 ++++++++++++++
 gcc/testsuite/g++.dg/reflect/contract5.C       | 26 ++++++++++
 gcc/testsuite/g++.dg/reflect/reflect_object2.C |  1 +
 8 files changed, 222 insertions(+), 3 deletions(-)

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 5cf101883a84..6191cfbd1682 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -17174,7 +17174,11 @@ tsubst_splice_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
   if (SPLICE_EXPR_MEMBER_ACCESS_P (t))
     gcc_assert (valid_splice_for_member_access_p (op, /*decls_only_p=*/false));
   else if (SPLICE_EXPR_EXPRESSION_P (t))
-    op = convert_from_reference (op);
+    {
+      op = convert_from_reference (op);
+      if (flag_contracts && processing_contract_condition)
+       op = constify_contract_access (op);
+    }
 
   return op;
 }
diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc
index 7e962fdc19e5..18261d9f10de 100644
--- a/gcc/cp/reflect.cc
+++ b/gcc/cp/reflect.cc
@@ -8782,12 +8782,14 @@ splice (tree refl)
       return error_mark_node;
     }
 
+  reflect_kind kind = REFLECT_EXPR_KIND (refl);
+  refl = REFLECT_EXPR_HANDLE (refl);
+
   /* We are bringing some entity from the unevaluated expressions world
      to possibly outside of that, mark it used.  */
-  if (!mark_used (REFLECT_EXPR_HANDLE (refl)))
+  if (!mark_used (refl))
     return error_mark_node;
 
-  refl = REFLECT_EXPR_HANDLE (refl);
   /* Function templates are wrapped in OVERLOAD from name lookup
      and a lot of places assume that.  Furthermore, if reflection comes
      from ^^fntmpl, it is wrapped with OVERLOAD already, only when
@@ -8799,6 +8801,10 @@ splice (tree refl)
      class for the access path.  */
   if (is_overloaded_fn (refl))
     refl = baselink_for_fns (refl, /*ignore_current_class_p=*/true);
+  /* For [:reflect_object(var):] wrap var into a VCE, so that it is an lvalue,
+     but not the var itself.  */
+  if (kind == REFLECT_OBJECT && DECL_P (refl))
+    refl = build1_loc (loc, VIEW_CONVERT_EXPR, TREE_TYPE (refl), refl);
 
   return refl;
 }
diff --git a/gcc/testsuite/g++.dg/reflect/contract1.C 
b/gcc/testsuite/g++.dg/reflect/contract1.C
new file mode 100644
index 000000000000..a0dcd8e4df3a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/contract1.C
@@ -0,0 +1,32 @@
+// C++26 P3598R0 - CWG 3158 - const-ification of Splice Expressions
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+namespace std
+{
+  template<typename T>
+    struct add_lvalue_reference
+    { using type = __add_lvalue_reference (T); };
+  template<typename T>
+    using add_lvalue_reference_t = typename add_lvalue_reference <T>::type;
+}
+
+#define UNCONST(x) const_cast <std::add_lvalue_reference_t <decltype (x)>> (x)
+
+int v;
+
+void
+foo (int p)
+  pre ((++p, true))            // { dg-error "increment of read-only location 
'\\\(const int\\\)p'" }
+  pre ((++[:^^p:], true))      // { dg-error "increment of read-only location 
'\\\(const int\\\)p'" }
+  pre ((++UNCONST (p), true))
+{
+}
+
+void
+bar ()
+  pre ((++v, true))            // { dg-error "increment of read-only location 
'\\\(const int\\\)v'" }
+  pre ((++[:^^v:], true))      // { dg-error "increment of read-only location 
'\\\(const int\\\)v'" }
+  pre ((++UNCONST (v), true))
+{
+}
diff --git a/gcc/testsuite/g++.dg/reflect/contract2.C 
b/gcc/testsuite/g++.dg/reflect/contract2.C
new file mode 100644
index 000000000000..db5762916694
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/contract2.C
@@ -0,0 +1,71 @@
+// C++26 P3598R0 - CWG 3158 - const-ification of Splice Expressions
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+namespace std
+{
+  template<typename T>
+    struct add_lvalue_reference
+    { using type = __add_lvalue_reference (T); };
+  template<typename T>
+    using add_lvalue_reference_t = typename add_lvalue_reference <T>::type;
+}
+
+#define UNCONST(x) const_cast <std::add_lvalue_reference_t <decltype (x)>> (x)
+
+template <typename T>
+void
+foo (T p)                      // { dg-error "increment of read-only location 
'\\\(const int\\\)p'" }
+  pre ((++p, true))
+{
+}
+
+template <typename T>
+void
+bar (T p)                      // { dg-error "increment of read-only location 
'\\\(const int\\\)p'" }
+  pre ((++[:^^p:], true))
+{
+}
+
+template <typename T>
+void
+baz (T p)
+  pre ((++UNCONST (p), true))
+{
+}
+
+int v;                         // { dg-error "increment of read-only location 
'\\\(const int\\\)v'" }
+int w;                         // { dg-error "increment of read-only location 
'\\\(const int\\\)w'" }
+int x;
+
+template <bool B>
+void
+corge (int p)                  // { dg-error "increment of read-only location 
'\\\(const int\\\)p'" }
+  pre ((++(B ? v : p), true))  // { dg-error "increment of read-only location 
'\\\(B \\\? \\\(\\\(const int\\\)v\\\) : \\\(\\\(const int\\\)p\\\)\\\)'" }
+{
+}
+
+template <bool B>
+void
+garply (int p)
+  pre ((++[:B ? ^^w : ^^p:], true))
+{
+}
+
+template <bool B>
+void
+waldo (int p)
+  pre ((++(B ? UNCONST (x) : UNCONST (p)), true))
+{
+}
+
+void
+qux (int p)
+{
+  foo (p);                     // { dg-message "required from here" }
+  bar (p);                     // { dg-message "required from here" }
+  baz (p);
+  corge <true> (p);            // { dg-message "required from here" }
+  garply <true> (p);           // { dg-message "required from here" }
+  waldo <true> (p);
+}
diff --git a/gcc/testsuite/g++.dg/reflect/contract3.C 
b/gcc/testsuite/g++.dg/reflect/contract3.C
new file mode 100644
index 000000000000..ca9aea2010cf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/contract3.C
@@ -0,0 +1,40 @@
+// C++26 P3598R0 - CWG 3158 - const-ification of Splice Expressions
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+int v;
+
+void
+foo ()
+  pre ((++[:object_of (^^v):], true))
+{
+}
+
+void
+bar ()
+  pre ((++[:std::meta::reflect_object (v):], true))
+{
+}
+
+template <bool B>
+void
+baz (int p)
+  pre ((++[:object_of (B ? ^^v : ^^p):], true))
+{
+}
+
+template <bool B>
+void
+qux (int p)
+  pre ((++[:std::meta::reflect_object (B ? v : p):], true))
+{
+}
+
+void
+corge (int p)
+{
+  baz <true> (p);
+  qux <true> (p);
+}
diff --git a/gcc/testsuite/g++.dg/reflect/contract4.C 
b/gcc/testsuite/g++.dg/reflect/contract4.C
new file mode 100644
index 000000000000..d1e6f433b21d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/contract4.C
@@ -0,0 +1,39 @@
+// C++26 P3598R0 - CWG 3158 - const-ification of Splice Expressions
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <array>
+#include <meta>
+#include <vector>
+
+template <typename T1, typename T2>
+auto
+foo (const T1 &t1, const T2 &t2)
+  pre([: is_integral_type (^^T1) ? ^^t2 : ^^t1 :]
+      [ [: is_integral_type (^^T1) ? ^^t1 : ^^t2 :] ] >= 0)
+{
+  constexpr auto key = is_integral_type (^^T1) ? ^^t1 : ^^t2;
+  constexpr auto container = is_integral_type (^^T1) ? ^^t2 : ^^t1;
+  return [: container :][ [: key :] ];
+}
+
+void
+bar (std::vector <int> &v)
+{
+  foo (v, 5);
+  foo (5, v);
+}
+
+template <std::size_t N>
+void
+baz (std::array <int, N> &v)
+{
+  foo (v, 5);
+  foo (5, v);
+}
+
+void
+qux (std::array <int, 7> &v)
+{
+  baz (v);
+}
diff --git a/gcc/testsuite/g++.dg/reflect/contract5.C 
b/gcc/testsuite/g++.dg/reflect/contract5.C
new file mode 100644
index 000000000000..229844789471
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/contract5.C
@@ -0,0 +1,26 @@
+// C++26 P3598R0 - CWG 3158 - const-ification of Splice Expressions
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <map>
+#include <meta>
+
+template <typename T1, typename T2>
+auto
+foo (const T1 &t1, const T2 &t2)
+  pre([: is_integral_type (^^T1) ? ^^t2 : ^^t1 :]
+      [ [: is_integral_type (^^T1) ? ^^t1 : ^^t2 :] ] >= 0)    // { dg-error 
"no match for 'operator\\\[\\\]' in 't1\\\[t2\\\]' \\\(operand types are 'const 
std::map<int, int>' and 'const int'\\\)" }
+{                                                              // { dg-error 
"no match for 'operator\\\[\\\]' in 't2\\\[t1\\\]' \\\(operand types are 'const 
std::map<int, int>' and 'const int'\\\)" "" { target *-*-* } .-1 }
+  constexpr auto key = is_integral_type (^^T1) ? ^^t1 : ^^t2;
+  constexpr auto container = is_integral_type (^^T1) ? ^^t2 : ^^t1;
+  return [: container :][ [: key :] ];                         // { dg-error 
"no match for 'operator\\\[\\\]' in 't1\\\[t2\\\]' \\\(operand types are 'const 
std::map<int, int>' and 'const int'\\\)" }
+}                                                              // { dg-error 
"no match for 'operator\\\[\\\]' in 't2\\\[t1\\\]' \\\(operand types are 'const 
std::map<int, int>' and 'const int'\\\)" "" { target *-*-* } .-1 }
+
+void
+bar (std::map <int, int> &m)
+{
+  foo (m, 5);
+  foo (5, m);
+}
+
+// { dg-error "cannot bind rvalue reference of type 'std::map<int, 
int>::key_type\\\&\\\&' {aka 'int\\\&\\\&'} to lvalue of type 'const int'" "" { 
target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/reflect/reflect_object2.C 
b/gcc/testsuite/g++.dg/reflect/reflect_object2.C
index adf17a20a8fe..cc452107491b 100644
--- a/gcc/testsuite/g++.dg/reflect/reflect_object2.C
+++ b/gcc/testsuite/g++.dg/reflect/reflect_object2.C
@@ -112,4 +112,5 @@ g ()
 {
   int i = [:robj:];
   [:rbar:](42);
+  [:robj:] += 42;
 }

Reply via email to