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
