From 68a31285be9e54743c924b192e760433c2172711 Mon Sep 17 00:00:00 2001 From: Mohit Mishra Date: Sun, 25 Oct 2026 03:37:33 +0530 Subject: [PATCH 1/2] Keep C++11 constexpr templates with C++14-style statements usable in constant expressions in GNU and Clang modes. [GH #356] --- .../gh-356-cpp11-constexpr-cpp14-body.txt | 18 +++++++++ src/statements.c | 28 ++++++++++---- .../tests/changes/.GH356.rto/default.1.1.txt | 37 +++++++++++++++++++ .../tests/changes/.GH356.rto/default.2.1.txt | 37 +++++++++++++++++++ tests/tests/changes/GH356.sft.cpp | 34 +++++++++++++++++ 5 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt create mode 100644 tests/tests/changes/.GH356.rto/default.1.1.txt create mode 100644 tests/tests/changes/.GH356.rto/default.2.1.txt create mode 100644 tests/tests/changes/GH356.sft.cpp diff --git a/src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt b/src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt new file mode 100644 index 0000000000..a5bfd7fd74 --- /dev/null +++ b/src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt @@ -0,0 +1,18 @@ +[GH #356] +C++11 constexpr templates with C++14-style statements + +In GNU (12.0 and later) and Clang (3.3 and later) C++11 modes, a statement +that C++14 allows in a constexpr function or constructor of a template (an +"if" statement, for example) is accepted with a warning. The function was +nevertheless treated as not constexpr, so it could not be used in constant +expressions. For example, with --c++11 --gnu_version 150000: + + template struct A { + T x; + constexpr A(T i) : x(i) { + if (sizeof(T) == 0) {} // Warning + } + }; + constexpr A a(5); // Previously an error. + +This is now fixed. diff --git a/src/statements.c b/src/statements.c index 9000e44085..6f93bbeef8 100644 --- a/src/statements.c +++ b/src/statements.c @@ -7806,6 +7806,7 @@ this statement was preceded by the GNU keyword __extension__. a_boolean prev_was_label = FALSE; a_boolean get_another_statement; a_boolean can_appear_in_constexpr_body = relaxed_constexpr_enabled; + a_boolean allowed_by_relaxed_constexpr = TRUE; an_error_severity in_constexpr_body_sev = es_error; a_source_position start_pos; an_il_entity_list_entry_ptr @@ -7891,13 +7892,15 @@ this statement was preceded by the GNU keyword __extension__. case tok_for_each: /* "for each" statement (ECMA-372 section 16.2.1). */ for_each_statement(); - can_appear_in_constexpr_body = FALSE; + can_appear_in_constexpr_body = allowed_by_relaxed_constexpr = FALSE; break; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ case tok_goto: /* Goto statement. */ goto_statement(); - if (!cpp23_mode) can_appear_in_constexpr_body = FALSE; + if (!cpp23_mode) { + can_appear_in_constexpr_body = allowed_by_relaxed_constexpr = FALSE; + } /* if */ break; case tok_continue: /* Continue statement. */ @@ -7921,7 +7924,7 @@ this statement was preceded by the GNU keyword __extension__. case tok_microsoft_asm: /* Asm "declaration" or Microsoft mode asm block. */ asm_statement(); - can_appear_in_constexpr_body = cpp20_mode; + can_appear_in_constexpr_body = allowed_by_relaxed_constexpr = cpp20_mode; if (!can_appear_in_constexpr_body && cpp14_mode && gpp_version_is(>= 100000)) { /* Newer versions of GCC only warn about asm declarations in constexpr @@ -7933,18 +7936,19 @@ this statement was preceded by the GNU keyword __extension__. /* C++ try block. */ try_block_statement((a_statement_ptr)NULL, /*explicit_return_type=*/FALSE); - can_appear_in_constexpr_body = constexpr_try_enabled; + can_appear_in_constexpr_body = allowed_by_relaxed_constexpr = + constexpr_try_enabled; break; #if MICROSOFT_EXTENSIONS_ALLOWED case tok_microsoft_try: /* Microsoft try-finally or try-except statement. */ microsoft_try_statement(); - can_appear_in_constexpr_body = FALSE; + can_appear_in_constexpr_body = allowed_by_relaxed_constexpr = FALSE; break; case tok_leave: /* Microsoft __leave. */ leave_statement(); - can_appear_in_constexpr_body = FALSE; + can_appear_in_constexpr_body = allowed_by_relaxed_constexpr = FALSE; break; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ case tok_case: @@ -8091,6 +8095,9 @@ this statement was preceded by the GNU keyword __extension__. sssep->prefix_attributes = NULL; } /* if */ decl_statement(marked_as_gnu_extension, &can_appear_in_constexpr_body); + /* Declarations are not checked against the C++14 rules here, so do + not assume that they would be valid in C++14. */ + allowed_by_relaxed_constexpr = can_appear_in_constexpr_body; if (!can_appear_in_constexpr_body && relaxed_constexpr_allowed()) { /* This can be true in Clang-C++11-mode system headers. */ in_constexpr_body_sev = es_warning; @@ -8144,6 +8151,7 @@ this statement was preceded by the GNU keyword __extension__. } /* switch */ if (current_rp != NULL && current_rp->is_constexpr && !can_appear_in_constexpr_body) { + a_boolean constexpr_still_possible = FALSE; if (current_rp->is_declared_constexpr || current_rp->is_consteval) { /* Report an error if the statement is not one that is allowed in a constexpr function or constexpr constructor. */ @@ -8156,6 +8164,10 @@ this statement was preceded by the GNU keyword __extension__. } else if ((gpp_version_is(>=120000) || clangcpp_version_is(>=30300)) && current_rp->is_template_function) { in_constexpr_body_sev = es_warning; + /* GCC and Clang accept a statement that C++14 allows as an + extension, and the function can still be used in constant + expressions. */ + constexpr_still_possible = allowed_by_relaxed_constexpr; } /* if */ pos_diagnostic(in_constexpr_body_sev, special_kind_is(current_rp, sfk_constructor) ? @@ -8163,7 +8175,9 @@ this statement was preceded by the GNU keyword __extension__. ec_invalid_statement_in_constexpr_function, &start_pos); } /* if */ - scope_stack[depth_innermost_function_scope].constexpr_ruled_out = TRUE; + if (!constexpr_still_possible) { + scope_stack[depth_innermost_function_scope].constexpr_ruled_out = TRUE; + } /* if */ } /* if */ /* Loop if we just got a label and not an actual statement. */ if (get_another_statement) { diff --git a/tests/tests/changes/.GH356.rto/default.1.1.txt b/tests/tests/changes/.GH356.rto/default.1.1.txt new file mode 100644 index 0000000000..28438cdf11 --- /dev/null +++ b/tests/tests/changes/.GH356.rto/default.1.1.txt @@ -0,0 +1,37 @@ +fe_only -DTEST_NUMBER=1 --c++11 --gnu_version 150000 Test_name.c +"Test_name.c", line 14: warning: statement may not appear in a constexpr constructor + if (sizeof(T) == 0) {} + ^ + +"Test_name.c", line 22: warning: statement may not appear in a constexpr function + if (t > 0) {} + ^ + +"Test_name.c", line 23: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 23: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 23: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 30: warning: statement may not appear in a constexpr function + goto done; + ^ + +"Test_name.c", line 31: warning: statement may not appear in a constexpr function + done: + ^ + +"Test_name.c", line 34: error: expression must have a constant value + static_assert(g(1) == 1, ""); // Error: g is not usable here. + ^ +"Test_name.c", line 34: note: this call cannot be evaluated because the target function function "g(T) [with T=int]" (declared at line 29) is not constexpr or not completely defined yet + static_assert(g(1) == 1, ""); // Error: g is not usable here. + ^ + +1 error detected in the compilation of "Test_name.c". diff --git a/tests/tests/changes/.GH356.rto/default.2.1.txt b/tests/tests/changes/.GH356.rto/default.2.1.txt new file mode 100644 index 0000000000..4247af1f36 --- /dev/null +++ b/tests/tests/changes/.GH356.rto/default.2.1.txt @@ -0,0 +1,37 @@ +fe_only -DTEST_NUMBER=2 --c++11 --clang_version 190000 Test_name.c +"Test_name.c", line 14: warning: statement may not appear in a constexpr constructor + if (sizeof(T) == 0) {} + ^ + +"Test_name.c", line 22: warning: statement may not appear in a constexpr function + if (t > 0) {} + ^ + +"Test_name.c", line 23: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 23: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 23: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 30: warning: statement may not appear in a constexpr function + goto done; + ^ + +"Test_name.c", line 31: warning: statement may not appear in a constexpr function + done: + ^ + +"Test_name.c", line 34: error: expression must have a constant value + static_assert(g(1) == 1, ""); // Error: g is not usable here. + ^ +"Test_name.c", line 34: note: this call cannot be evaluated because the target function function "g(T) [with T=int]" (declared at line 29) is not constexpr or not completely defined yet + static_assert(g(1) == 1, ""); // Error: g is not usable here. + ^ + +1 error detected in the compilation of "Test_name.c". diff --git a/tests/tests/changes/GH356.sft.cpp b/tests/tests/changes/GH356.sft.cpp new file mode 100644 index 0000000000..3167a76fa8 --- /dev/null +++ b/tests/tests/changes/GH356.sft.cpp @@ -0,0 +1,34 @@ +//type:fn +//options:--c++11 --gnu_version 150000:--c++11 --clang_version 190000 +//remark:[GH #356] C++11 constexpr template with a C++14-style body +// +// In GNU and Clang C++11 modes, a statement that C++14 allows in a constexpr +// function template only gets a warning. The function can still be used in +// constant expressions. A goto (not allowed in C++14 either) still makes +// the function unusable in constant expressions. + +template +struct A { + T x; + constexpr A(T i) : x(i) { + if (sizeof(T) == 0) {} + } +}; +constexpr A a(5); +static_assert(a.x == 5, ""); + +template +constexpr int f(T t) { + if (t > 0) {} + switch (sizeof(T)) { default: break; } + return 1; +} +static_assert(f(1) == 1, ""); + +template +constexpr int g(T t) { + goto done; +done: + return 1; +} +static_assert(g(1) == 1, ""); // Error: g is not usable here. From bead231fa3a0b1ec2867309554404bb987539798 Mon Sep 17 00:00:00 2001 From: Mohit Mishra Date: Sun, 25 Oct 2026 11:20:33 +0530 Subject: [PATCH 2/2] Match GCC and Clang for C++14-style statements in C++11 constexpr functions. [GH #356] --- .../gh-356-cpp11-constexpr-cpp14-body.txt | 12 ++++--- src/statements.c | 12 ++++--- .../tests/changes/.GH356.rto/default.1.1.txt | 31 +++++++++++++------ .../tests/changes/.GH356.rto/default.2.1.txt | 22 +++++++------ tests/tests/changes/GH356.sft.cpp | 17 +++++++--- 5 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt b/src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt index a5bfd7fd74..992570dadc 100644 --- a/src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt +++ b/src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt @@ -2,10 +2,10 @@ C++11 constexpr templates with C++14-style statements In GNU (12.0 and later) and Clang (3.3 and later) C++11 modes, a statement -that C++14 allows in a constexpr function or constructor of a template (an -"if" statement, for example) is accepted with a warning. The function was -nevertheless treated as not constexpr, so it could not be used in constant -expressions. For example, with --c++11 --gnu_version 150000: +that C++14 allows in a constexpr constructor or function of a template (an +"if" statement, for example) is accepted with a warning. The constructor or +function was nevertheless treated as not constexpr, so it could not be used +in constant expressions. For example, with --c++11 --gnu_version 150000: template struct A { T x; @@ -15,4 +15,6 @@ expressions. For example, with --c++11 --gnu_version 150000: }; constexpr A a(5); // Previously an error. -This is now fixed. +This is now fixed for constexpr constructors and, in Clang mode (like +Clang), for constexpr functions. As with GCC, such a constexpr function +still cannot be used in constant expressions in GNU mode. diff --git a/src/statements.c b/src/statements.c index 6f93bbeef8..67aab8bbdc 100644 --- a/src/statements.c +++ b/src/statements.c @@ -8164,10 +8164,14 @@ this statement was preceded by the GNU keyword __extension__. } else if ((gpp_version_is(>=120000) || clangcpp_version_is(>=30300)) && current_rp->is_template_function) { in_constexpr_body_sev = es_warning; - /* GCC and Clang accept a statement that C++14 allows as an - extension, and the function can still be used in constant - expressions. */ - constexpr_still_possible = allowed_by_relaxed_constexpr; + /* GCC and Clang accept a statement that C++14 allows in a constexpr + constructor as an extension, and the constructor can still be used + in constant expressions. Clang does the same for a constexpr + function; GCC does not. */ + constexpr_still_possible = + allowed_by_relaxed_constexpr && + (clangcpp_version_is(>=30300) || + special_kind_is(current_rp, sfk_constructor)); } /* if */ pos_diagnostic(in_constexpr_body_sev, special_kind_is(current_rp, sfk_constructor) ? diff --git a/tests/tests/changes/.GH356.rto/default.1.1.txt b/tests/tests/changes/.GH356.rto/default.1.1.txt index 28438cdf11..57079ff4cf 100644 --- a/tests/tests/changes/.GH356.rto/default.1.1.txt +++ b/tests/tests/changes/.GH356.rto/default.1.1.txt @@ -1,37 +1,48 @@ fe_only -DTEST_NUMBER=1 --c++11 --gnu_version 150000 Test_name.c -"Test_name.c", line 14: warning: statement may not appear in a constexpr constructor +"Test_name.c", line 15: warning: statement may not appear in a constexpr constructor if (sizeof(T) == 0) {} ^ -"Test_name.c", line 22: warning: statement may not appear in a constexpr function +"Test_name.c", line 24: warning: statement may not appear in a constexpr constructor + constexpr B(T i) : x(i) { return; } + ^ + +"Test_name.c", line 31: warning: statement may not appear in a constexpr function if (t > 0) {} ^ -"Test_name.c", line 23: warning: statement may not appear in a constexpr function +"Test_name.c", line 32: warning: statement may not appear in a constexpr function switch (sizeof(T)) { default: break; } ^ -"Test_name.c", line 23: warning: statement may not appear in a constexpr function +"Test_name.c", line 32: warning: statement may not appear in a constexpr function switch (sizeof(T)) { default: break; } ^ -"Test_name.c", line 23: warning: statement may not appear in a constexpr function +"Test_name.c", line 32: warning: statement may not appear in a constexpr function switch (sizeof(T)) { default: break; } ^ -"Test_name.c", line 30: warning: statement may not appear in a constexpr function +"Test_name.c", line 35: error: expression must have a constant value + static_assert(f(1) == 1, ""); // Error in GNU mode only. + ^ +"Test_name.c", line 35: note: this call cannot be evaluated because the target function function "f(T) [with T=int]" (declared at line 30) is not constexpr or not completely defined yet + static_assert(f(1) == 1, ""); // Error in GNU mode only. + ^ + +"Test_name.c", line 39: warning: statement may not appear in a constexpr function goto done; ^ -"Test_name.c", line 31: warning: statement may not appear in a constexpr function +"Test_name.c", line 40: warning: statement may not appear in a constexpr function done: ^ -"Test_name.c", line 34: error: expression must have a constant value +"Test_name.c", line 43: error: expression must have a constant value static_assert(g(1) == 1, ""); // Error: g is not usable here. ^ -"Test_name.c", line 34: note: this call cannot be evaluated because the target function function "g(T) [with T=int]" (declared at line 29) is not constexpr or not completely defined yet +"Test_name.c", line 43: note: this call cannot be evaluated because the target function function "g(T) [with T=int]" (declared at line 38) is not constexpr or not completely defined yet static_assert(g(1) == 1, ""); // Error: g is not usable here. ^ -1 error detected in the compilation of "Test_name.c". +2 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/changes/.GH356.rto/default.2.1.txt b/tests/tests/changes/.GH356.rto/default.2.1.txt index 4247af1f36..fce27818b9 100644 --- a/tests/tests/changes/.GH356.rto/default.2.1.txt +++ b/tests/tests/changes/.GH356.rto/default.2.1.txt @@ -1,36 +1,40 @@ fe_only -DTEST_NUMBER=2 --c++11 --clang_version 190000 Test_name.c -"Test_name.c", line 14: warning: statement may not appear in a constexpr constructor +"Test_name.c", line 15: warning: statement may not appear in a constexpr constructor if (sizeof(T) == 0) {} ^ -"Test_name.c", line 22: warning: statement may not appear in a constexpr function +"Test_name.c", line 24: warning: statement may not appear in a constexpr constructor + constexpr B(T i) : x(i) { return; } + ^ + +"Test_name.c", line 31: warning: statement may not appear in a constexpr function if (t > 0) {} ^ -"Test_name.c", line 23: warning: statement may not appear in a constexpr function +"Test_name.c", line 32: warning: statement may not appear in a constexpr function switch (sizeof(T)) { default: break; } ^ -"Test_name.c", line 23: warning: statement may not appear in a constexpr function +"Test_name.c", line 32: warning: statement may not appear in a constexpr function switch (sizeof(T)) { default: break; } ^ -"Test_name.c", line 23: warning: statement may not appear in a constexpr function +"Test_name.c", line 32: warning: statement may not appear in a constexpr function switch (sizeof(T)) { default: break; } ^ -"Test_name.c", line 30: warning: statement may not appear in a constexpr function +"Test_name.c", line 39: warning: statement may not appear in a constexpr function goto done; ^ -"Test_name.c", line 31: warning: statement may not appear in a constexpr function +"Test_name.c", line 40: warning: statement may not appear in a constexpr function done: ^ -"Test_name.c", line 34: error: expression must have a constant value +"Test_name.c", line 43: error: expression must have a constant value static_assert(g(1) == 1, ""); // Error: g is not usable here. ^ -"Test_name.c", line 34: note: this call cannot be evaluated because the target function function "g(T) [with T=int]" (declared at line 29) is not constexpr or not completely defined yet +"Test_name.c", line 43: note: this call cannot be evaluated because the target function function "g(T) [with T=int]" (declared at line 38) is not constexpr or not completely defined yet static_assert(g(1) == 1, ""); // Error: g is not usable here. ^ diff --git a/tests/tests/changes/GH356.sft.cpp b/tests/tests/changes/GH356.sft.cpp index 3167a76fa8..79e5a75fa9 100644 --- a/tests/tests/changes/GH356.sft.cpp +++ b/tests/tests/changes/GH356.sft.cpp @@ -3,9 +3,10 @@ //remark:[GH #356] C++11 constexpr template with a C++14-style body // // In GNU and Clang C++11 modes, a statement that C++14 allows in a constexpr -// function template only gets a warning. The function can still be used in -// constant expressions. A goto (not allowed in C++14 either) still makes -// the function unusable in constant expressions. +// constructor of a template only gets a warning, and the constructor can +// still be used in constant expressions. For a constexpr function template, +// that is only the case in Clang mode (GCC rejects the call). A goto, which +// C++14 does not allow either, makes the function unusable in both modes. template struct A { @@ -17,13 +18,21 @@ struct A { constexpr A a(5); static_assert(a.x == 5, ""); +template +struct B { + T x; + constexpr B(T i) : x(i) { return; } +}; +constexpr B b(6); +static_assert(b.x == 6, ""); + template constexpr int f(T t) { if (t > 0) {} switch (sizeof(T)) { default: break; } return 1; } -static_assert(f(1) == 1, ""); +static_assert(f(1) == 1, ""); // Error in GNU mode only. template constexpr int g(T t) {