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..992570dadc --- /dev/null +++ b/src/pending_changes/gh-356-cpp11-constexpr-cpp14-body.txt @@ -0,0 +1,20 @@ +[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 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; + constexpr A(T i) : x(i) { + if (sizeof(T) == 0) {} // Warning + } + }; + constexpr A a(5); // Previously an error. + +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 9000e44085..67aab8bbdc 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,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 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) ? @@ -8163,7 +8179,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..57079ff4cf --- /dev/null +++ b/tests/tests/changes/.GH356.rto/default.1.1.txt @@ -0,0 +1,48 @@ +fe_only -DTEST_NUMBER=1 --c++11 --gnu_version 150000 Test_name.c +"Test_name.c", line 15: warning: statement may not appear in a constexpr constructor + if (sizeof(T) == 0) {} + ^ + +"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 32: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 32: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 32: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"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 40: warning: statement may not appear in a constexpr function + done: + ^ + +"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 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. + ^ + +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 new file mode 100644 index 0000000000..fce27818b9 --- /dev/null +++ b/tests/tests/changes/.GH356.rto/default.2.1.txt @@ -0,0 +1,41 @@ +fe_only -DTEST_NUMBER=2 --c++11 --clang_version 190000 Test_name.c +"Test_name.c", line 15: warning: statement may not appear in a constexpr constructor + if (sizeof(T) == 0) {} + ^ + +"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 32: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 32: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 32: warning: statement may not appear in a constexpr function + switch (sizeof(T)) { default: break; } + ^ + +"Test_name.c", line 39: warning: statement may not appear in a constexpr function + goto done; + ^ + +"Test_name.c", line 40: warning: statement may not appear in a constexpr function + done: + ^ + +"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 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". diff --git a/tests/tests/changes/GH356.sft.cpp b/tests/tests/changes/GH356.sft.cpp new file mode 100644 index 0000000000..79e5a75fa9 --- /dev/null +++ b/tests/tests/changes/GH356.sft.cpp @@ -0,0 +1,43 @@ +//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 +// 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 { + T x; + constexpr A(T i) : x(i) { + if (sizeof(T) == 0) {} + } +}; +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, ""); // Error in GNU mode only. + +template +constexpr int g(T t) { + goto done; +done: + return 1; +} +static_assert(g(1) == 1, ""); // Error: g is not usable here.