On Thu, Jan 29, 2026 at 9:50 PM Patrick Palka <[email protected]> wrote:
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this
> look OK for trunk and ideally backports? The parent libstdc++
> regression PR114865 is present in all release branches, and
> this would allow us to fix it everywhere.
>
> Note that there is a potential library-only solution for
> PR114865, namely adding an empty [[no_unique_address]] data member
> to std::atomic and through its member-initializer sneaking in the
> __builtin_clear_padding call, roughly
>
> template<typename _Tp>
> struct atomic
> {
> alignas(_S_alignment) _Tp _M_i;
> [[no_unique_address]] struct __empty { } _M_empty;
>
> constexpr atomic(_Tp __t)
> : _M_i(__t),
> _M_empty(((__atomic_impl::__maybe_has_padding<_Tp>()
> && !std::__is_constant_evaluated()
> ? __builtin_clear_padding(&_M_i)
> : void()), __empty{}))
> { }
> };
>
> None of us is confident that adding such a member to atomic is safe
> from an ABI perspective (and e.g. safe from triggering a
> [[no_unique_address]] bug on the release branches). It seems this
> frontend approach of first relaxing the C++11 constexpr rules is safer
> to backport, all things considered.
>
> -- >8 --
>
> This patch extends our support for C++14 non-empty constexpr
> constructor bodies to C++11, as an extension. This will make
> it trivial to safely fix the C++11 library regression PR114865
> which requires calling __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 already desugar member initializers to statements internally
> even in C++11 mode, and so their bodies are already effectively non-empty
> internally, 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 it's only valid in C++20).
>
We could enable the missing_mem_inits check in C++11 mode also to address
that, so the warning would be trully for C++14 extensions.
> 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 for rejecting invalid
> constructors (e.g. missing initializers), and actual evaluation uses
> the desugared constructor body.
>
> With this patch we can fix PR114865 by defining the affected
> constructor as (avoding if statements which are still disallowed
> in C++11 constexpr):
>
> constexpr atomic(_Tp __i) noexcept : _M_i(__i)
> {
> (__atomic_impl::__maybe_has_padding<_Tp>()
> && !std::__is_constant_evaluated()
> ? __builtin_clear_padding(std::__addressof(_M_i))
> : void());
> }
>
> (Note that Clang accepts the code as an extension in C++11 mode as
> well.)
>
> 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.
> ---
> 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(-)
> create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-ctor23.C
>
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index 1527e9dcbac8..a31fe6b113a9 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -412,11 +412,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));
> @@ -578,11 +574,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;
> };
> --
> 2.53.0.rc1.65.gea24e2c554
>
>