Hi! The following patch attempts to implement the C++26 P3533R2 - constexpr virtual inheritance paper. The changes include not rejecting it for C++26, tweaking the error wording to show that it is valid in C++26, adjusting synthesized_method_walk not to make synthetized cdtors non-constexpr just because of virtual base classes in C++26 and various tweaks in constexpr.cc so that it can deal with the expressions used for virtual base member accesses or cdtor calls which need __in_chrg and/or __vtt_parm arguments to be passed in some cases implicitly when they aren't passed explicitly.
There are two places where I'm not sure what to do: 1) one can be seen on the constexpr-ice21.C testcase: struct NoMut1 { int a, b; }; struct NoMut3 : virtual NoMut1 { constexpr NoMut3(int a, int b) : NoMut1{a, b} {} }; void mutable_subobjects() { constexpr NoMut3 nm3 = {1, 2}; struct A { void f() { static_assert(nm3.a == 1, ""); // ERROR here: "local variable" } }; } The two other errors on the testcase are expectedly gone with C++26, but the last one remains. The problem is that when parsing nm3.a inside of mutable_subobjects()::A::f() build_class_member_access_expr calls build_base_path which calls cp_build_addr_expr and that makes nm3 odr-used. I must say I have no idea whether nm3 ought to be odr-used or not just because of nm3.a use and if not, how that should be changed. Plus whether the fact if nm3.a is odr-use or not is somehow affected by the (so far unimplemented) part of P2686R4 paper. 2) another one can be seen on the constexpr-dynamic10.C testcase struct C { virtual void a(); }; struct B { virtual void b(); }; struct A : virtual B, C { virtual void c(); }; constexpr A a; constexpr bool b1 = (dynamic_cast<C&>((B&)a), false); // ERROR here "reference 'dynamic_cast' failed" I think the error is incorrect here, because struct C { virtual void a(); }; struct B { virtual void b(); }; struct A : virtual B, C { virtual void c(); }; A a; bool b1 = (dynamic_cast<C&>((B&)a), false); int main () { C &c = dynamic_cast<C&>((B&)a); C &c2 = dynamic_cast<C&>(a); } works at runtime. In the patch I've adjusted the function comment of cxx_eval_dynamic_cast_fn because with virtual bases I believe hint -1 might be possible, though I'm afraid I don't know enough about dynamic_cast and cxx_eval_dynamic_cast_fn to figure out what needs to change there. It is hint -2 that fails, not hint -1. In any case, this has been successfully bootstrapped/regtested on x86_64-linux and i686-linux. 2025-06-24 Jakub Jelinek <ja...@redhat.com> PR c++/120777 gcc/c-family/ * c-cppbuiltin.cc (c_cpp_builtins): Predefine __cpp_constexpr_virtual_inheritance=202506L for C++26. gcc/cp/ * constexpr.cc: Implement C++26 P3533R2 - constexpr virtual inheritance. (is_valid_constexpr_fn): Don't reject constexpr cdtors in classes with virtual bases for C++26, adjust error wording. (cxx_bind_parameters_in_call): Add ORIG_FUN argument, add values for __in_chrg and __vtt_parm arguments when needed. (cxx_eval_dynamic_cast_fn): Adjust function comment, HINT -1 should be possible. (cxx_eval_call_expression): Add orig_fun variable, set it to fun before looking through clones, pass it to cxx_bind_parameters_in_call. (reduced_constant_expression_p): Add SZ argument, pass DECL_SIZE of FIELD_DECL e.index to recursive calls and don't return false if SZ is non-NULL and there are unfilled fields with bit position at or above SZ. (cxx_fold_indirect_ref_1): Handle reading of vtables using ptrdiff_t dynamic type instead of some pointer type. Set el_sz to DECL_SIZE_UNIT value rather than TYPE_SIZE_UNIT of DECL_FIELD_IS_BASE fields in classes with virtual bases. (cxx_fold_indirect_ref): In canonicalize_obj_off lambda look through COMPONENT_REFs with DECL_FIELD_IS_BASE in classes with virtual bases and adjust off correspondingly. * cp-tree.h (reduced_constant_expression_p): Add another tree argument defaulted to NULL_TREE. * method.cc (synthesized_method_walk): Don't clear *constexpr_p if there are virtual bases for C++26. gcc/testsuite/ * g++.dg/cpp26/constexpr-virt-inherit1.C: New test. * g++.dg/cpp26/constexpr-virt-inherit2.C: New test. * g++.dg/cpp26/feat-cxx26.C: Add __cpp_constexpr_virtual_inheritance tersts. * g++.dg/cpp2a/constexpr-dtor16.C: Don't expect errors for C++26. * g++.dg/cpp2a/constexpr-dynamic10.C: Don't expect 2 errors for C++26, temporarily accept one invalid one. * g++.dg/cpp0x/constexpr-ice21.C: Don't expect 2 errors for C++26. * g++.dg/cpp0x/constexpr-ice4.C: Don't expect any errors for C++26. --- gcc/c-family/c-cppbuiltin.cc.jj 2025-06-23 15:57:14.746215440 +0200 +++ gcc/c-family/c-cppbuiltin.cc 2025-06-23 18:35:02.776544246 +0200 @@ -1094,6 +1094,7 @@ c_cpp_builtins (cpp_reader *pfile) cpp_define (pfile, "__cpp_variadic_friend=202403L"); cpp_define (pfile, "__cpp_pack_indexing=202311L"); cpp_define (pfile, "__cpp_pp_embed=202502L"); + cpp_define (pfile, "__cpp_constexpr_virtual_inheritance=202506L"); } if (flag_concepts && cxx_dialect > cxx14) cpp_define (pfile, "__cpp_concepts=202002L"); --- gcc/cp/cp-tree.h.jj 2025-06-17 13:19:02.979959124 +0200 +++ gcc/cp/cp-tree.h 2025-06-23 21:17:58.569652869 +0200 @@ -8912,7 +8912,7 @@ extern tree fold_non_dependent_init (tr bool = false, tree = NULL_TREE); extern tree fold_simple (tree); extern tree fold_to_constant (tree); -extern bool reduced_constant_expression_p (tree); +extern bool reduced_constant_expression_p (tree, tree = NULL_TREE); extern bool is_instantiation_of_constexpr (tree); extern bool var_in_constexpr_fn (tree); extern bool var_in_maybe_constexpr_fn (tree); --- gcc/cp/method.cc.jj 2025-06-03 07:49:37.861644267 +0200 +++ gcc/cp/method.cc 2025-06-24 10:54:41.306557859 +0200 @@ -3024,7 +3024,7 @@ synthesized_method_walk (tree ctype, spe /* Vbase cdtors are not relevant. */; else { - if (constexpr_p) + if (constexpr_p && cxx_dialect < cxx26) *constexpr_p = false; FOR_EACH_VEC_ELT (*vbases, i, base_binfo) --- gcc/cp/constexpr.cc.jj 2025-06-23 15:57:14.748215414 +0200 +++ gcc/cp/constexpr.cc 2025-06-24 12:28:49.561105734 +0200 @@ -303,17 +303,19 @@ is_valid_constexpr_fn (tree fun, bool co } } } - else if (CLASSTYPE_VBASECLASSES (DECL_CONTEXT (fun))) + else if (CLASSTYPE_VBASECLASSES (DECL_CONTEXT (fun)) && cxx_dialect < cxx26) { ret = false; if (complain) { if (DECL_CONSTRUCTOR_P (fun)) error ("%<constexpr%> constructor in %q#T that has " - "virtual base classes", DECL_CONTEXT (fun)); + "virtual base classes only available with " + "%<-std=c++2c%> or %<-std=gnu++2c%>", DECL_CONTEXT (fun)); else error ("%<constexpr%> destructor in %q#T that has " - "virtual base classes", DECL_CONTEXT (fun)); + "virtual base classes only available with " + "%<-std=c++2c%> or %<-std=gnu++2c%>", DECL_CONTEXT (fun)); } } @@ -1879,20 +1881,28 @@ addr_of_non_const_var (tree *tp, int *wa static tree cxx_bind_parameters_in_call (const constexpr_ctx *ctx, tree t, tree fun, - bool *non_constant_p, bool *overflow_p, - bool *non_constant_args) + tree orig_fun, bool *non_constant_p, + bool *overflow_p, bool *non_constant_args) { - const int nargs = call_expr_nargs (t); + int nargs = call_expr_nargs (t); tree parms = DECL_ARGUMENTS (fun); - int i; + int i, j = 0; + if (DECL_HAS_IN_CHARGE_PARM_P (fun) && fun != orig_fun) + ++nargs; + if (DECL_HAS_VTT_PARM_P (fun) + && fun != orig_fun + && (DECL_COMPLETE_CONSTRUCTOR_P (orig_fun) + || DECL_COMPLETE_DESTRUCTOR_P (orig_fun))) + ++nargs; /* We don't record ellipsis args below. */ int nparms = list_length (parms); int nbinds = nargs < nparms ? nargs : nparms; tree binds = make_tree_vec (nbinds); /* The call is not a constant expression if it involves the cdtor for a type - with virtual bases. */ - if (DECL_HAS_IN_CHARGE_PARM_P (fun) || DECL_HAS_VTT_PARM_P (fun)) + with virtual bases before C++26. */ + if (cxx_dialect < cxx26 + && (DECL_HAS_IN_CHARGE_PARM_P (fun) || DECL_HAS_VTT_PARM_P (fun))) { if (!ctx->quiet) { @@ -1910,7 +1920,30 @@ cxx_bind_parameters_in_call (const const tree type = parms ? TREE_TYPE (parms) : void_type_node; if (parms && DECL_BY_REFERENCE (parms)) type = TREE_TYPE (type); - x = get_nth_callarg (t, i); + if (i == 1 + && j == 0 + && DECL_HAS_IN_CHARGE_PARM_P (fun) + && orig_fun != fun) + { + if (DECL_COMPLETE_CONSTRUCTOR_P (orig_fun) + || DECL_COMPLETE_DESTRUCTOR_P (orig_fun)) + x = boolean_true_node; + else + x = boolean_false_node; + j = -1; + } + else if (i == 2 + && j == -1 + && DECL_HAS_VTT_PARM_P (fun) + && orig_fun != fun + && (DECL_COMPLETE_CONSTRUCTOR_P (orig_fun) + || DECL_COMPLETE_DESTRUCTOR_P (orig_fun))) + { + x = build_zero_cst (type); + j = -2; + } + else + x = get_nth_callarg (t, i + j); /* For member function, the first argument is a pointer to the implied object. For a constructor, it might still be a dummy object, in which case we get the real argument from ctx. */ @@ -2529,10 +2562,7 @@ get_component_with_type (tree path, tree dst_ptr + src2dst == src_ptr -1: unspecified relationship -2: src_type is not a public base of dst_type - -3: src_type is a multiple public non-virtual base of dst_type - - Since literal types can't have virtual bases, we only expect hint >=0, - -2, or -3. */ + -3: src_type is a multiple public non-virtual base of dst_type */ static tree cxx_eval_dynamic_cast_fn (const constexpr_ctx *ctx, tree call, @@ -2916,6 +2946,7 @@ cxx_eval_call_expression (const constexp *non_constant_p = true; return t; } + tree orig_fun = fun; if (DECL_CLONED_FUNCTION_P (fun) && !DECL_DELETING_DESTRUCTOR_P (fun)) fun = DECL_CLONED_FUNCTION (fun); @@ -3110,7 +3141,7 @@ cxx_eval_call_expression (const constexp bool non_constant_args = false; constexpr_call new_call; new_call.bindings - = cxx_bind_parameters_in_call (ctx, t, fun, non_constant_p, + = cxx_bind_parameters_in_call (ctx, t, fun, orig_fun, non_constant_p, overflow_p, &non_constant_args); /* We build up the bindings list before we know whether we already have this @@ -3514,11 +3545,12 @@ cxx_eval_call_expression (const constexp /* Return true if T is a valid constant initializer. If a CONSTRUCTOR initializes all the members, the CONSTRUCTOR_NO_CLEARING flag will be - cleared. + cleared. If called recursively on a FIELD_DECL's CONSTRUCTOR, SZ + is DECL_SIZE of the FIELD_DECL, otherwise NULL. FIXME speed this up, it's taking 16% of compile time on sieve testcase. */ bool -reduced_constant_expression_p (tree t) +reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */) { if (t == NULL_TREE) return false; @@ -3586,7 +3618,12 @@ reduced_constant_expression_p (tree t) { /* If VAL is null, we're in the middle of initializing this element. */ - if (!reduced_constant_expression_p (e.value)) + if (!reduced_constant_expression_p (e.value, + (e.index + && (TREE_CODE (e.index) + == FIELD_DECL)) + ? DECL_SIZE (e.index) + : NULL_TREE)) return false; /* We want to remove initializers for empty fields in a struct to avoid confusing output_constructor. */ @@ -3606,7 +3643,16 @@ reduced_constant_expression_p (tree t) /* There could be a non-empty field at the end. */ for (; field; field = next_subobject_field (DECL_CHAIN (field))) if (!is_really_empty_class (TREE_TYPE (field), /*ignore_vptr*/false)) - return false; + { + /* Ignore FIELD_DECLs with bit positions beyond DECL_SIZE of + the parent FIELD_DECL (if any) for classes with virtual + bases. */ + if (cxx_dialect >= cxx26 + && sz + && tree_int_cst_le (sz, bit_position (field))) + break; + return false; + } ok: if (CONSTRUCTOR_NO_CLEARING (t)) /* All the fields are initialized. */ @@ -5868,6 +5914,20 @@ cxx_fold_indirect_ref_1 (const constexpr unsigned HOST_WIDE_INT const_nunits; if (off == 0 && similar_type_p (optype, type)) return op; + else if (cxx_dialect >= cxx26 + && VAR_P (op) + && DECL_VTABLE_OR_VTT_P (op) + && same_type_ignoring_top_level_qualifiers_p (type, + ptrdiff_type_node) + && POINTER_TYPE_P (strip_array_types (optype))) + { + /* We often read some virtual table elements using ptrdiff_t rather + than pointer type. */ + if (tree ret = cxx_fold_indirect_ref_1 (ctx, loc, + strip_array_types (optype), + op, off, empty_base)) + return fold_convert (type, ret); + } else if (TREE_CODE (optype) == COMPLEX_TYPE && similar_type_p (type, TREE_TYPE (optype))) { @@ -5961,8 +6021,13 @@ cxx_fold_indirect_ref_1 (const constexpr if (!tree_fits_uhwi_p (pos)) continue; unsigned HOST_WIDE_INT upos = tree_to_uhwi (pos); - unsigned HOST_WIDE_INT el_sz - = tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (field))); + unsigned HOST_WIDE_INT el_sz; + if (DECL_FIELD_IS_BASE (field) + && CLASS_TYPE_P (optype) + && CLASSTYPE_VBASECLASSES (optype)) + el_sz = tree_to_uhwi (DECL_SIZE_UNIT (field)); + else + el_sz = tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (field))); if (upos <= off && off < upos + el_sz) { tree cop = build3 (COMPONENT_REF, TREE_TYPE (field), @@ -6013,6 +6078,25 @@ cxx_fold_indirect_ref (const constexpr_c offset positive, so that cxx_fold_indirect_ref_1 can identify more folding opportunities. */ auto canonicalize_obj_off = [] (tree& obj, tree& off) { + if (cxx_dialect >= cxx26) + { + /* For C++26, we need to fold *(B *)(&x.D.1234 + 32) used + to access virtual base members. */ + tree nobj = obj; + while (TREE_CODE (nobj) == COMPONENT_REF + && DECL_FIELD_IS_BASE (TREE_OPERAND (nobj, 1))) + nobj = TREE_OPERAND (nobj, 0); + if (nobj != obj + && CLASS_TYPE_P (TREE_TYPE (nobj)) + && CLASSTYPE_VBASECLASSES (TREE_TYPE (nobj))) + while (obj != nobj) + { + tree field = TREE_OPERAND (obj, 1); + tree pos = byte_position (field); + off = int_const_binop (PLUS_EXPR, off, pos); + obj = TREE_OPERAND (obj, 0); + } + } while (TREE_CODE (obj) == COMPONENT_REF /* We need to preserve union member accesses so that we can later properly diagnose accessing the wrong member. */ --- gcc/testsuite/g++.dg/cpp26/constexpr-virt-inherit1.C.jj 2025-06-23 20:13:23.617647761 +0200 +++ gcc/testsuite/g++.dg/cpp26/constexpr-virt-inherit1.C 2025-06-23 21:47:35.068198053 +0200 @@ -0,0 +1,189 @@ +// C++26 P3533R2 - constexpr virtual inheritance +// { dg-do compile { target c++26 } } + +struct A { + int a; + constexpr virtual int foo () { return a; }; + constexpr A () : a (42) {} + constexpr A (int x) : a (x) {} + constexpr virtual ~A () { if (a < 42 || a > 62) asm (""); } +}; +struct B : public A { + int b; + constexpr virtual int foo () { return a + b; } + constexpr B () : A (43), b (42) {} + constexpr B (int x, int y) : A (x), b (y) {} + constexpr virtual ~B () { if (b < 42 || b > 62) asm (""); } +}; +struct C : virtual public B { + int c; + constexpr C () : B (44, 43), c (45) {} + constexpr C (int x) : B (44, 43), c (x) {} + constexpr virtual int bar () { return a + b + c; } + constexpr virtual ~C () { if (c < 42 || c > 62) asm (""); } +}; +struct D : virtual public B { + int d; + constexpr D () : B (44, 43), d (45) {} + constexpr D (int x) : B (44, 43), d (x) {} + constexpr virtual int baz () { return a + b + d; } + constexpr virtual ~D () { if (d < 42 || d > 62) asm (""); } +}; +struct E : public C, D { + int e; + constexpr E () : B (), C (), D (), e (58) {} + constexpr E (int x, int y, int z, int w, int v) : B (x, y), C (z), D (w), e (v) {} + constexpr virtual ~E () { if (e < 42 || e > 62) asm (""); } +}; + +constexpr bool +qux () +{ + E f (45, 46, 47, 48, 49); + f.a++; + f.b++; + f.c++; + f.d++; + f.e++; + C *c = static_cast <C *> (&f); + D *d = static_cast <D *> (&f); + B *b = static_cast <B *> (&f); + A *a = static_cast <A *> (&f); + if (f.foo () != 46 + 47) + return false; + if (f.bar () != 46 + 47 + 48) + return false; + if (f.baz () != 46 + 47 + 49) + return false; + a->a += 2; + b->b += 3; + c->c += 4; + c->a += 5; + d->d += 6; + d->a += 7; + if (c->foo () != 60 + 50) + return false; + c->b -= 3; + if (d->foo () != 60 + 47) + return false; + if (f.a != 60 || f.b != 47 || f.c != 52 || f.d != 55 || f.e != 50) + return false; + C g (48); + c = static_cast <C *> (&g); + b = static_cast <B *> (&g); + a = static_cast <A *> (&g); + g.a++; + g.b++; + g.c++; + if (g.foo () != 45 + 44) + return false; + if (g.bar () != 45 + 44 + 49) + return false; + a->a += 2; + b->b += 3; + c->c += 4; + if (c->foo () != 47 + 47) + return false; + if (g.a != 47 || g.b != 47 || g.c != 53) + return false; + D h (49); + d = static_cast <D *> (&h); + b = static_cast <B *> (&h); + a = static_cast <A *> (&h); + h.a++; + h.b++; + h.d++; + if (h.foo () != 45 + 44) + return false; + if (h.baz () != 45 + 44 + 50) + return false; + a->a += 2; + b->b += 3; + d->d += 4; + if (d->foo () != 47 + 47) + return false; + if (h.a != 47 || h.b != 47 || h.d != 54) + return false; + return true; +} + +constexpr bool +corge () +{ + E *f = new E (45, 46, 47, 48, 49); + f->a++; + f->b++; + f->c++; + f->d++; + f->e++; + C *c = static_cast <C *> (f); + D *d = static_cast <D *> (f); + B *b = static_cast <B *> (f); + A *a = static_cast <A *> (f); + if (f->foo () != 46 + 47) + return false; + if (f->bar () != 46 + 47 + 48) + return false; + if (f->baz () != 46 + 47 + 49) + return false; + a->a += 2; + b->b += 3; + c->c += 4; + c->a += 5; + d->d += 6; + d->a += 7; + if (c->foo () != 60 + 50) + return false; + c->b -= 3; + if (d->foo () != 60 + 47) + return false; + if (f->a != 60 || f->b != 47 || f->c != 52 || f->d != 55 || f->e != 50) + return false; + C *g = new C (48); + c = static_cast <C *> (g); + b = static_cast <B *> (g); + a = static_cast <A *> (g); + g->a++; + g->b++; + g->c++; + if (g->foo () != 45 + 44) + return false; + if (g->bar () != 45 + 44 + 49) + return false; + a->a += 2; + b->b += 3; + c->c += 4; + if (c->foo () != 47 + 47) + return false; + if (g->a != 47 || g->b != 47 || g->c != 53) + return false; + D *h = new D (49); + d = static_cast <D *> (h); + b = static_cast <B *> (h); + a = static_cast <A *> (h); + h->a++; + h->b++; + h->d++; + if (h->foo () != 45 + 44) + return false; + if (h->baz () != 45 + 44 + 50) + return false; + a->a += 2; + b->b += 3; + d->d += 4; + if (d->foo () != 47 + 47) + return false; + if (h->a != 47 || h->b != 47 || h->d != 54) + return false; + delete h; + delete g; + delete f; + return true; +} + +static_assert (qux ()); +static_assert (corge ()); +constexpr E a; +constexpr E b (45, 46, 47, 48, 49); +constexpr C c; +constexpr C d (50); --- gcc/testsuite/g++.dg/cpp26/constexpr-virt-inherit2.C.jj 2025-06-24 10:56:07.642430579 +0200 +++ gcc/testsuite/g++.dg/cpp26/constexpr-virt-inherit2.C 2025-06-24 10:58:44.014388836 +0200 @@ -0,0 +1,18 @@ +// C++26 P3533R2 - constexpr virtual inheritance +// { dg-do compile { target c++26 } } + +struct A { int a; }; +struct B { int b; }; +struct C : virtual public A, B { int c; }; + +constexpr C +foo () +{ + C c; + c.a = 1; + c.b = 2; + c.c = 3; + return c; +} + +static_assert (foo ().a == 1 && foo ().b == 2 && foo ().c == 3); --- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj 2025-06-17 13:18:52.979091237 +0200 +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2025-06-23 20:10:45.486631704 +0200 @@ -634,3 +634,9 @@ #elif __cpp_pp_embed != 202502 # error "__cpp_pp_embed != 202502" #endif + +#ifndef __cpp_constexpr_virtual_inheritance +# error "__cpp_constexpr_virtual_inheritance" +#elif __cpp_constexpr_virtual_inheritance != 202506 +# error "__cpp_constexpr_virtual_inheritance != 202506" +#endif --- gcc/testsuite/g++.dg/cpp2a/constexpr-dtor16.C.jj 2025-04-18 11:23:53.870174278 +0200 +++ gcc/testsuite/g++.dg/cpp2a/constexpr-dtor16.C 2025-06-24 10:16:01.394915120 +0200 @@ -3,5 +3,5 @@ struct A { virtual ~A (); }; struct B : virtual A { constexpr ~B () {} }; -// { dg-error "'constexpr' destructor in 'struct B' that has virtual base classes" "" { target c++20 } .-1 } +// { dg-error "'constexpr' destructor in 'struct B' that has virtual base classes" "" { target { c++20 && c++23_down } } .-1 } // { dg-error "'constexpr' destructors only available with" "" { target c++17_down } .-2 } --- gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic10.C.jj 2025-04-18 11:23:53.870174278 +0200 +++ gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic10.C 2025-06-24 12:32:35.299213775 +0200 @@ -5,8 +5,8 @@ struct C { virtual void a(); }; struct B { virtual void b(); }; -struct A : virtual B, C { virtual void c(); }; // { dg-error "virtual base classes" } +struct A : virtual B, C { virtual void c(); }; // { dg-error "virtual base classes" "" { target c++23_down } } -constexpr A a; // { dg-error "call" } +constexpr A a; // { dg-error "call" "" { target c++23_down } } -constexpr bool b1 = (dynamic_cast<C&>((B&)a), false); +constexpr bool b1 = (dynamic_cast<C&>((B&)a), false); // { dg-error "reference 'dynamic_cast' failed" "" { target c++26 } } --- gcc/testsuite/g++.dg/cpp0x/constexpr-ice21.C.jj 2025-04-17 10:56:43.362325247 +0200 +++ gcc/testsuite/g++.dg/cpp0x/constexpr-ice21.C 2025-06-24 10:16:01.438914543 +0200 @@ -5,10 +5,10 @@ struct NoMut1 { int a, b; }; struct NoMut3 : virtual NoMut1 { constexpr NoMut3(int a, int b) : NoMut1{a, b} - {} // { dg-error "virtual base" } + {} // { dg-error "virtual base" "" { target c++23_down } } }; void mutable_subobjects() { - constexpr NoMut3 nm3 = {1, 2}; // { dg-error "call to non" } + constexpr NoMut3 nm3 = {1, 2}; // { dg-error "call to non" "" { target c++23_down } } struct A { void f() { static_assert(nm3.a == 1, ""); // { dg-error "local variable" } --- gcc/testsuite/g++.dg/cpp0x/constexpr-ice4.C.jj 2020-01-12 11:54:37.060403647 +0100 +++ gcc/testsuite/g++.dg/cpp0x/constexpr-ice4.C 2025-06-24 10:16:01.414914857 +0200 @@ -5,5 +5,5 @@ struct A {}; struct B : virtual A { - constexpr B() { } // { dg-error "has virtual base classes" } + constexpr B() { } // { dg-error "has virtual base classes" "" { target c++23_down } } }; Jakub