https://gcc.gnu.org/g:bdb7fa18765484c2fe97bb6ba9af63682eddb395
commit r15-10874-gbdb7fa18765484c2fe97bb6ba9af63682eddb395 Author: Patrick Palka <[email protected]> Date: Fri Jan 30 15:25:43 2026 -0500 c++: non-empty constexpr constructor bodies in C++11 [PR123845] This patch makes us support C++14 non-empty constexpr constructor bodies in C++11, as an extension. This will make it trivial to safely fix the C++11 library regression PR114865 that requires us to do __builtin_clear_padding after initializing _M_i in std::atomic's single-parameter constructor, and that's not really possible with the C++11 constexpr restrictions. Since we lower member initializers to constructor body statements internally, and so constructor bodies are already effectively non-empty internally even in C++11, supporting non-empty bodies in user code is mostly a matter of relaxing the parse-time error. But constexpr-ex3.C revealed that by accepting the non-empty body of A's constructor, build_data_member_initialization goes on to mistake the 'i = _i' assignment as a member initializer, and we incorrectly accept the constructor in C++11 mode (even though omitting mem-inits is only valid since C++20). Turns out this is caused by that function recognizing MODIFY_EXPR only in C++11 mode, logic that was last changed by r5-5013 (presumably to limit impact of the patch at the time) but I reckon could just be removed outright. This should be safe because the result of build_data_member_initialization is only used by cx_check_missing_mem_inits for validation; evaluation is in terms of the entire lowered constructor body. PR c++/123845 PR libstdc++/114865 gcc/cp/ChangeLog: * constexpr.cc (build_data_member_initialization): Remove C++11-specific recognition of MODIFY_EXPR. (check_constexpr_ctor_body): Relax error diagnostic to a pedwarn and don't clear DECL_DECLARED_CONSTEXPR_P upon error. Return true if complaining. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/constexpr-ex3.C: Adjust C++11 non-empty constexpr constructor dg-error to a dg-warning. Expect a follow-up missing member initializer diagnostic in C++11 mode. * g++.dg/cpp2a/constexpr-try1.C: Expect a follow-up compound-statement in constexpr function diagnostic in C++11 mode. * g++.dg/cpp2a/constexpr-try2.C: Likewise. Adjust C++11 non-empty constexpr constructor dg-error to a dg-warning. * g++.dg/cpp2a/constexpr-try3.C: Adjust C++11 non-empty constexpr constructor dg-error to a dg-warning. * g++.dg/cpp0x/constexpr-ctor23.C: New test. Reviewed-by: Jason Merrill <[email protected]> (cherry picked from commit e4c57e146a224d0aaa71ace78f96fca1156add24) Diff: --- gcc/cp/constexpr.cc | 14 +++++--------- gcc/testsuite/g++.dg/cpp0x/constexpr-ctor23.C | 26 ++++++++++++++++++++++++++ gcc/testsuite/g++.dg/cpp0x/constexpr-ex3.C | 3 ++- gcc/testsuite/g++.dg/cpp2a/constexpr-try1.C | 1 + gcc/testsuite/g++.dg/cpp2a/constexpr-try2.C | 3 ++- gcc/testsuite/g++.dg/cpp2a/constexpr-try3.C | 2 +- 6 files changed, 37 insertions(+), 12 deletions(-) diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 0d30e5c7d9cc..a5078fc2fd67 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -399,11 +399,7 @@ build_data_member_initialization (tree t, vec<constructor_elt, va_gc> **vec) } if (TREE_CODE (t) == CONVERT_EXPR) t = TREE_OPERAND (t, 0); - if (TREE_CODE (t) == INIT_EXPR - /* vptr initialization shows up as a MODIFY_EXPR. In C++14 we only - use what this function builds for cx_check_missing_mem_inits, and - assignment in the ctor body doesn't count. */ - || (cxx_dialect < cxx14 && TREE_CODE (t) == MODIFY_EXPR)) + if (TREE_CODE (t) == INIT_EXPR) { member = TREE_OPERAND (t, 0); init = break_out_target_exprs (TREE_OPERAND (t, 1)); @@ -565,11 +561,11 @@ check_constexpr_ctor_body (tree last, tree list, bool complain) else if (list != last && !check_constexpr_ctor_body_1 (last, list)) ok = false; - if (!ok) + if (!ok && complain) { - if (complain) - error ("%<constexpr%> constructor does not have empty body"); - DECL_DECLARED_CONSTEXPR_P (current_function_decl) = false; + pedwarn (input_location, OPT_Wc__14_extensions, + "%<constexpr%> constructor does not have empty body"); + ok = true; } return ok; } diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor23.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor23.C new file mode 100644 index 000000000000..4019804ab166 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor23.C @@ -0,0 +1,26 @@ +// Verify we diagnose and accept, as an extension, a non-empty constexpr +// constructor body in C++11 mode. +// PR c++/123845 +// { dg-do compile { target c++11_only } } +// { dg-options "" } + +constexpr int negate(int n) { return -n; } + +struct A { + int m; + constexpr A() : m(42) { + ++m; + m = negate(m); + } // { dg-warning "does not have empty body \[-Wc++14-extensions\]" } +}; +static_assert(A().m == -43, ""); + +template<class T> +struct B { + int m; + constexpr B() : m(42) { + ++m; + m = negate(m); + } // { dg-warning "does not have empty body \[-Wc++14-extensions\]" } +}; +static_assert(B<int>().m == -43, ""); diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ex3.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ex3.C index 9d6d5ff587ca..169976afbaf7 100644 --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ex3.C +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ex3.C @@ -6,7 +6,8 @@ struct A { int i; - constexpr A(int _i) { i = _i; } // { dg-error "empty body|A::i" "" { target c++17_down } } + constexpr A(int _i) { i = _i; } // { dg-warning "empty body" "" { target c++11_only } } + // { dg-error "'A::i' must be init" "" { target c++17_down } .-1 } }; template <class T> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-try1.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-try1.C index 977eb86dd192..e5e70a62b50f 100644 --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-try1.C +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-try1.C @@ -32,6 +32,7 @@ struct S { } catch (int) { // { dg-error "compound-statement in 'constexpr' function" "" { target c++11_only } } } // { dg-error "compound-statement in 'constexpr' function" "" { target c++11_only } .-2 } } catch (...) { // { dg-error "'constexpr' constructor does not have empty body" "" { target c++11_only } } + // { dg-error "compound-statement in 'constexpr' function" "" { target c++11_only } .-1 } } int m; }; diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-try2.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-try2.C index 7ca7261a9e00..9504fdaa8696 100644 --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-try2.C +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-try2.C @@ -32,7 +32,8 @@ struct S { try { // { dg-warning "'try' in 'constexpr' function only available with" "" { target c++17_down } } } catch (int) { // { dg-warning "compound-statement in 'constexpr' function" "" { target c++11_only } } } // { dg-warning "compound-statement in 'constexpr' function" "" { target c++11_only } .-2 } - } catch (...) { // { dg-error "'constexpr' constructor does not have empty body" "" { target c++11_only } } + } catch (...) { // { dg-warning "'constexpr' constructor does not have empty body" "" { target c++11_only } } + // { dg-warning "compound-statement in 'constexpr' function" "" { target c++11_only } .-1 } } int m; }; diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-try3.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-try3.C index ab7e8f6d4649..070040c5deef 100644 --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-try3.C +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-try3.C @@ -31,7 +31,7 @@ struct S { try { // { dg-warning "'try' in 'constexpr' function only available with" "" { target c++17_down } } } catch (int) { } - } catch (...) { // { dg-error "'constexpr' constructor does not have empty body" "" { target c++11_only } } + } catch (...) { // { dg-warning "'constexpr' constructor does not have empty body" "" { target c++11_only } } } int m; };
