Hi!

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.

So far lightly tested (on
GXX_TESTSUITE_STDS=98,11,14,17,20,23,26,29 make check-g++ -j32 -k 
RUNTESTFLAGS="dg.exp='reflect/*'"
), ok for trunk if it passes full bootstrap/regtest?

2026-09-10  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.

--- a/gcc/cp/reflect.cc 2026-08-20 08:39:02.400320178 +0200
+++ b/gcc/cp/reflect.cc 2026-09-10 15:54:22.535416674 +0200
@@ -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 (VIEW_CONVERT_EXPR, TREE_TYPE (refl), refl);
 
   return refl;
 }
--- a/gcc/cp/pt.cc      2026-09-10 15:13:06.631450280 +0200
+++ b/gcc/cp/pt.cc      2026-09-10 16:10:14.607111179 +0200
@@ -17174,7 +17174,11 @@ tsubst_splice_expr (tree t, tree args, t
   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;
 }
--- a/gcc/testsuite/g++.dg/reflect/contract1.C  2026-09-10 15:13:14.746781204 
+0200
+++ b/gcc/testsuite/g++.dg/reflect/contract1.C  2026-09-10 16:14:24.660891506 
+0200
@@ -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))
+{
+}
--- a/gcc/testsuite/g++.dg/reflect/contract2.C  2026-09-10 15:13:14.746898983 
+0200
+++ b/gcc/testsuite/g++.dg/reflect/contract2.C  2026-09-10 16:27:15.532724804 
+0200
@@ -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);
+}
--- a/gcc/testsuite/g++.dg/reflect/contract3.C  2026-09-10 15:13:33.709088988 
+0200
+++ b/gcc/testsuite/g++.dg/reflect/contract3.C  2026-09-10 16:30:30.859154428 
+0200
@@ -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);
+}
--- a/gcc/testsuite/g++.dg/reflect/contract4.C  2026-09-10 16:31:10.752629451 
+0200
+++ b/gcc/testsuite/g++.dg/reflect/contract4.C  2026-09-10 15:14:46.432118660 
+0200
@@ -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);
+}
--- a/gcc/testsuite/g++.dg/reflect/contract5.C  2026-09-10 16:31:15.872562075 
+0200
+++ b/gcc/testsuite/g++.dg/reflect/contract5.C  2026-09-10 15:35:07.447779324 
+0200
@@ -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 }
--- a/gcc/testsuite/g++.dg/reflect/reflect_object2.C    2026-03-27 
10:17:16.140298005 +0100
+++ b/gcc/testsuite/g++.dg/reflect/reflect_object2.C    2026-09-10 
15:53:51.973822831 +0200
@@ -112,4 +112,5 @@ g ()
 {
   int i = [:robj:];
   [:rbar:](42);
+  [:robj:] += 42;
 }

        Jakub

Reply via email to