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; }
