c++: P2448 - Relaxing some constexpr restrictions [PR106649]

This patch implements C++23 P2448, which lifts more restrictions on the
constexpr keyword.  It's effectively going the way of being just a hint
(hello, inline!).

This gist is relatively simple: in C++23, a constexpr function's return
type/parameter type doesn't have to be a literal type; and you can have
a constexpr function for which no invocation satisfies the requirements
of a core constant expression.  For example,

  void f(int& i); // not constexpr

  constexpr void g(int& i) {
    f(i); // unconditionally calls a non-constexpr function
  }

is now OK, even though there isn't an invocation of 'g' that would be
a constant expression.  Maybe 'f' will be made constexpr soon, or maybe
this depends on the version of C++ used, and similar.  The patch is
unfortunately not that trivial.  The important bit is to use the new
require_potential_rvalue_constant_expression_fncheck in
maybe_save_constexpr_fundef (and where appropriate).  It has a new flag
that says that we're checking the body of a constexpr function, and in
that case it's OK to find constructs that aren't a constant expression.

Since it's useful to be able to check for problematic constructs even
in C++23, this patch implements a new warning, -Winvalid-constexpr,
which is a pedwarn turned on by default in C++20 and earlier, and which
can be turned on in C++23 as well, in which case it's an ordinary warning.
This I implemented by using the new function constexpr_error, used in
p_c_e_1 and friends.  (In some cases I believe fundef_p will be always
false (= hard error), but it made sense to me to be consistent and use
constexpr_error throughout p_c_e_1.)

While working on this I think I found a bug, see constexpr-nonlit15.C
and <https://gcc.gnu.org/PR107598>.  This patch doesn't address that.

This patch includes changes to diagnose the problem if the user doesn't
use -Winvalid-constexpr and calls a constexpr function that in fact isn't
constexpr-ready yet: maybe_save_constexpr_fundef registers the function
if warn_invalid_constexpr is 0 and explain_invalid_constexpr_fn then
gives the diagnostic.

	PR c++/106649

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update value of __cpp_constexpr for
	C++23.
	* c-opts.cc (c_common_post_options): Set warn_invalid_constexpr
	depending on cxx_dialect.
	* c.opt (Winvalid-constexpr): New option.

gcc/cp/ChangeLog:

	* constexpr.cc (constexpr_error): New function.
	(is_valid_constexpr_fn): Use constexpr_error.
	(maybe_save_constexpr_fundef): Call
	require_potential_rvalue_constant_expression_fncheck rather than
	require_potential_rvalue_constant_expression.  Register the
	function if -Wno-invalid-constexpr was specified.
	(explain_invalid_constexpr_fn): Don't return early if a function marked
	'constexpr' that isn't actually a constant expression was called.
	(non_const_var_error): Add a bool parameter.  Use constexpr_error.
	(inline_asm_in_constexpr_error): Likewise.
	(cxx_eval_constant_expression): Adjust calls to non_const_var_error
	and inline_asm_in_constexpr_error.
	(potential_constant_expression_1): Add a bool parameter.  Use
	constexpr_error.
	(require_potential_rvalue_constant_expression_fncheck): New function.
	* cp-tree.h (require_potential_rvalue_constant_expression_fncheck):
	Declare.
	* method.cc (struct comp_info): Call
	require_potential_rvalue_constant_expression_fncheck rather than
	require_potential_rvalue_constant_expression.

gcc/ChangeLog:

	* doc/invoke.texi: Document -Winvalid-constexpr.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/constexpr-ctor2.C: Expect an error in c++20_down only.
	* g++.dg/cpp0x/constexpr-default-ctor.C: Likewise.
	* g++.dg/cpp0x/constexpr-diag3.C: Likewise.
	* g++.dg/cpp0x/constexpr-ex1.C: Likewise.
	* g++.dg/cpp0x/constexpr-friend.C: Likewise.
	* g++.dg/cpp0x/constexpr-generated1.C: Likewise.
	* g++.dg/cpp0x/constexpr-ice5.C: Likewise.
	* g++.dg/cpp0x/constexpr-ice6.C: Likewise.
	* g++.dg/cpp0x/constexpr-memfn1.C: Likewise.
	* g++.dg/cpp0x/constexpr-neg2.C: Likewise.
	* g++.dg/cpp0x/constexpr-non-const-arg.C: Likewise.
	* g++.dg/cpp0x/constexpr-reinterpret1.C: Likewise.
	* g++.dg/cpp0x/pr65327.C: Likewise.
	* g++.dg/cpp1y/constexpr-105050.C: Likewise.
	* g++.dg/cpp1y/constexpr-89285-2.C: Likewise.
	* g++.dg/cpp1y/constexpr-89285.C: Likewise.
	* g++.dg/cpp1y/constexpr-89785-2.C: Likewise.
	* g++.dg/cpp1y/constexpr-neg1.C: Likewise.
	* g++.dg/cpp1y/constexpr-nsdmi7b.C: Likewise.
	* g++.dg/cpp1y/constexpr-throw.C: Likewise.
	* g++.dg/cpp23/constexpr-nonlit3.C: Remove dg-error.
	* g++.dg/cpp23/constexpr-nonlit6.C: Call the test functions.
	* g++.dg/cpp23/feat-cxx2b.C: Adjust the expected value of
	__cpp_constexpr.
	* g++.dg/cpp2a/consteval3.C: Remove dg-error.
	* g++.dg/cpp2a/constexpr-new7.C: Expect an error in c++20_down only.
	* g++.dg/cpp2a/constexpr-try5.C: Remove dg-error.
	* g++.dg/cpp2a/spaceship-constexpr1.C: Expect an error in c++20_down
	only.
	* g++.dg/cpp2a/spaceship-eq3.C: Likewise.
	* g++.dg/diagnostic/constexpr1.C: Remove dg-error.
	* g++.dg/gomp/pr79664.C: Use -Winvalid-constexpr -pedantic-errors.
	* g++.dg/ubsan/vptr-4.C: Likewise.
	* g++.dg/cpp23/constexpr-nonlit10.C: New test.
	* g++.dg/cpp23/constexpr-nonlit11.C: New test.
	* g++.dg/cpp23/constexpr-nonlit12.C: New test.
	* g++.dg/cpp23/constexpr-nonlit13.C: New test.
	* g++.dg/cpp23/constexpr-nonlit14.C: New test.
	* g++.dg/cpp23/constexpr-nonlit15.C: New test.
	* g++.dg/cpp23/constexpr-nonlit16.C: New test.
	* g++.dg/cpp23/constexpr-nonlit8.C: New test.
	* g++.dg/cpp23/constexpr-nonlit9.C: New test.
This commit is contained in:
Marek Polacek 2022-11-02 13:11:02 -04:00
parent dbdce6adb7
commit c85f8dbb17
47 changed files with 715 additions and 146 deletions

View File

@ -1074,7 +1074,7 @@ c_cpp_builtins (cpp_reader *pfile)
/* Set feature test macros for C++23. */
cpp_define (pfile, "__cpp_size_t_suffix=202011L");
cpp_define (pfile, "__cpp_if_consteval=202106L");
cpp_define (pfile, "__cpp_constexpr=202110L");
cpp_define (pfile, "__cpp_constexpr=202207L");
cpp_define (pfile, "__cpp_multidimensional_subscript=202211L");
cpp_define (pfile, "__cpp_named_character_escapes=202207L");
cpp_define (pfile, "__cpp_static_call_operator=202207L");

View File

@ -1059,6 +1059,10 @@ c_common_post_options (const char **pfilename)
if (flag_sized_deallocation == -1)
flag_sized_deallocation = (cxx_dialect >= cxx14);
/* Pedwarn about invalid constexpr functions before C++23. */
if (warn_invalid_constexpr == -1)
warn_invalid_constexpr = (cxx_dialect < cxx23);
/* char8_t support is implicitly enabled in C++20 and C2X. */
if (flag_char8_t == -1)
flag_char8_t = (cxx_dialect >= cxx20) || flag_isoc2x;

View File

@ -817,6 +817,10 @@ Wint-to-pointer-cast
C ObjC C++ ObjC++ Var(warn_int_to_pointer_cast) Init(1) Warning
Warn when there is a cast to a pointer from an integer of a different size.
Winvalid-constexpr
C++ ObjC++ Var(warn_invalid_constexpr) Init(-1) Warning
Warn when a function never produces a constant expression.
Winvalid-offsetof
C++ ObjC++ Var(warn_invalid_offsetof) Init(1) Warning
Warn about invalid uses of the \"offsetof\" macro.

View File

@ -139,6 +139,42 @@ ensure_literal_type_for_constexpr_object (tree decl)
return decl;
}
/* Issue a diagnostic with text GMSGID for constructs that are invalid in
constexpr functions. CONSTEXPR_FUNDEF_P is true if we're checking
a constexpr function body; if so, don't report hard errors and issue
a pedwarn pre-C++23, or a warning in C++23, if requested by
-Winvalid-constexpr. Otherwise, we're not in the context where we are
checking if a function can be marked 'constexpr', so give a hard error. */
ATTRIBUTE_GCC_DIAG(3,4)
static bool
constexpr_error (location_t location, bool constexpr_fundef_p,
const char *gmsgid, ...)
{
diagnostic_info diagnostic;
va_list ap;
rich_location richloc (line_table, location);
va_start (ap, gmsgid);
bool ret;
if (!constexpr_fundef_p)
{
/* Report an error that cannot be suppressed. */
diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_ERROR);
ret = diagnostic_report_diagnostic (global_dc, &diagnostic);
}
else if (warn_invalid_constexpr)
{
diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
cxx_dialect < cxx23 ? DK_PEDWARN : DK_WARNING);
diagnostic.option_index = OPT_Winvalid_constexpr;
ret = diagnostic_report_diagnostic (global_dc, &diagnostic);
}
else
ret = false;
va_end (ap);
return ret;
}
struct constexpr_fundef_hasher : ggc_ptr_hash<constexpr_fundef>
{
static hashval_t hash (const constexpr_fundef *);
@ -208,9 +244,11 @@ is_valid_constexpr_fn (tree fun, bool complain)
if (complain)
{
auto_diagnostic_group d;
error ("invalid type for parameter %d of %<constexpr%> "
"function %q+#D", DECL_PARM_INDEX (parm), fun);
explain_non_literal_class (TREE_TYPE (parm));
if (constexpr_error (input_location, /*constexpr_fundef_p*/true,
"invalid type for parameter %d of "
"%<constexpr%> function %q+#D",
DECL_PARM_INDEX (parm), fun))
explain_non_literal_class (TREE_TYPE (parm));
}
}
}
@ -242,9 +280,10 @@ is_valid_constexpr_fn (tree fun, bool complain)
if (complain)
{
auto_diagnostic_group d;
error ("invalid return type %qT of %<constexpr%> function %q+D",
rettype, fun);
explain_non_literal_class (rettype);
if (constexpr_error (input_location, /*constexpr_fundef_p*/true,
"invalid return type %qT of %<constexpr%> "
"function %q+D", rettype, fun))
explain_non_literal_class (rettype);
}
}
@ -918,7 +957,7 @@ maybe_save_constexpr_fundef (tree fun)
bool potential = potential_rvalue_constant_expression (massaged);
if (!potential && complain)
require_potential_rvalue_constant_expression (massaged);
require_potential_rvalue_constant_expression_fncheck (massaged);
if (DECL_CONSTRUCTOR_P (fun) && potential
&& !DECL_DEFAULTED_FN (fun))
@ -933,11 +972,16 @@ maybe_save_constexpr_fundef (tree fun)
massaged = DECL_SAVED_TREE (fun);
potential = potential_rvalue_constant_expression (massaged);
if (!potential && complain)
require_potential_rvalue_constant_expression (massaged);
require_potential_rvalue_constant_expression_fncheck (massaged);
}
}
if (!potential && complain)
if (!potential && complain
/* If -Wno-invalid-constexpr was specified, we haven't complained
about non-constant expressions yet. Register the function and
complain in explain_invalid_constexpr_fn if the function is
called. */
&& warn_invalid_constexpr != 0)
return;
if (implicit)
@ -996,19 +1040,27 @@ register_constexpr_fundef (const constexpr_fundef &value)
**slot = value;
}
/* FUN is a non-constexpr function called in a context that requires a
constant expression. If it comes from a constexpr template, explain why
the instantiation isn't constexpr. */
/* FUN is a non-constexpr (or, with -Wno-invalid-constexpr, a constexpr
function called in a context that requires a constant expression).
If it comes from a constexpr template, explain why the instantiation
isn't constexpr. Otherwise, explain why the function cannot be used
in a constexpr context. */
void
explain_invalid_constexpr_fn (tree fun)
{
static hash_set<tree> *diagnosed;
tree body;
/* In C++23, a function marked 'constexpr' may not actually be a constant
expression. We haven't diagnosed the problem yet: -Winvalid-constexpr
wasn't enabled. The function was called, so diagnose why it cannot be
used in a constant expression. */
if (warn_invalid_constexpr == 0 && DECL_DECLARED_CONSTEXPR_P (fun))
/* Go on. */;
/* Only diagnose defaulted functions, lambdas, or instantiations. */
if (!DECL_DEFAULTED_FN (fun)
&& !LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun))
&& !is_instantiation_of_constexpr (fun))
else if (!DECL_DEFAULTED_FN (fun)
&& !LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun))
&& !is_instantiation_of_constexpr (fun))
{
inform (DECL_SOURCE_LOCATION (fun), "%qD declared here", fun);
return;
@ -5612,11 +5664,12 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
}
/* Complain about R, a VAR_DECL, not being usable in a constant expression.
FUNDEF_P is true if we're checking a constexpr function body.
Shared between potential_constant_expression and
cxx_eval_constant_expression. */
static void
non_const_var_error (location_t loc, tree r)
non_const_var_error (location_t loc, tree r, bool fundef_p)
{
auto_diagnostic_group d;
tree type = TREE_TYPE (r);
@ -5625,20 +5678,21 @@ non_const_var_error (location_t loc, tree r)
|| DECL_NAME (r) == heap_vec_uninit_identifier
|| DECL_NAME (r) == heap_vec_identifier)
{
error_at (loc, "the content of uninitialized storage is not usable "
"in a constant expression");
inform (DECL_SOURCE_LOCATION (r), "allocated here");
if (constexpr_error (loc, fundef_p, "the content of uninitialized "
"storage is not usable in a constant expression"))
inform (DECL_SOURCE_LOCATION (r), "allocated here");
return;
}
if (DECL_NAME (r) == heap_deleted_identifier)
{
error_at (loc, "use of allocated storage after deallocation in a "
"constant expression");
inform (DECL_SOURCE_LOCATION (r), "allocated here");
if (constexpr_error (loc, fundef_p, "use of allocated storage after "
"deallocation in a constant expression"))
inform (DECL_SOURCE_LOCATION (r), "allocated here");
return;
}
error_at (loc, "the value of %qD is not usable in a constant "
"expression", r);
if (!constexpr_error (loc, fundef_p, "the value of %qD is not usable in "
"a constant expression", r))
return;
/* Avoid error cascade. */
if (DECL_INITIAL (r) == error_mark_node)
return;
@ -6697,15 +6751,17 @@ lookup_placeholder (const constexpr_ctx *ctx, value_cat lval, tree type)
return ob;
}
/* Complain about an attempt to evaluate inline assembly. */
/* Complain about an attempt to evaluate inline assembly. If FUNDEF_P is
true, we're checking a constexpr function body. */
static void
inline_asm_in_constexpr_error (location_t loc)
inline_asm_in_constexpr_error (location_t loc, bool fundef_p)
{
auto_diagnostic_group d;
error_at (loc, "inline assembly is not a constant expression");
inform (loc, "only unevaluated inline assembly is allowed in a "
"%<constexpr%> function in C++20");
if (constexpr_error (loc, fundef_p, "inline assembly is not a "
"constant expression"))
inform (loc, "only unevaluated inline assembly is allowed in a "
"%<constexpr%> function in C++20");
}
/* We're getting the constant value of DECL in a manifestly constant-evaluated
@ -6983,7 +7039,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
if (DECL_P (r))
{
if (!ctx->quiet)
non_const_var_error (loc, r);
non_const_var_error (loc, r, /*fundef_p*/false);
*non_constant_p = true;
}
break;
@ -7874,7 +7930,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
case ASM_EXPR:
if (!ctx->quiet)
inline_asm_in_constexpr_error (loc);
inline_asm_in_constexpr_error (loc, /*constexpr_fundef_p*/false);
*non_constant_p = true;
return t;
@ -8759,7 +8815,8 @@ check_for_return_continue (tree *tp, int *walk_subtrees, void *data)
diagnostic as appropriate under control of FLAGS. If WANT_RVAL is true,
an lvalue-rvalue conversion is implied. If NOW is true, we want to
consider the expression in the current context, independent of constexpr
substitution.
substitution. If FUNDEF_P is true, we're checking a constexpr function body
and hard errors should not be reported by constexpr_error.
C++0x [expr.const] used to say
@ -8776,10 +8833,12 @@ check_for_return_continue (tree *tp, int *walk_subtrees, void *data)
static bool
potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
tsubst_flags_t flags, tree *jump_target)
bool fundef_p, tsubst_flags_t flags,
tree *jump_target)
{
#define RECUR(T,RV) \
potential_constant_expression_1 ((T), (RV), strict, now, flags, jump_target)
potential_constant_expression_1 ((T), (RV), strict, now, fundef_p, flags, \
jump_target)
enum { any = false, rval = true };
int i;
@ -8801,8 +8860,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (TREE_THIS_VOLATILE (t) && want_rval)
{
if (flags & tf_error)
error_at (loc, "lvalue-to-rvalue conversion of a volatile lvalue "
"%qE with type %qT", t, TREE_TYPE (t));
constexpr_error (loc, fundef_p, "lvalue-to-rvalue conversion of "
"a volatile lvalue %qE with type %qT", t,
TREE_TYPE (t));
return false;
}
if (CONSTANT_CLASS_P (t))
@ -8861,7 +8921,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
/* An empty class has no data to read. */
return true;
if (flags & tf_error)
error ("%qE is not a constant expression", t);
constexpr_error (input_location, fundef_p,
"%qE is not a constant expression", t);
return false;
}
return true;
@ -8910,7 +8971,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
{
/* fold_call_expr can't do anything with IFN calls. */
if (flags & tf_error)
error_at (loc, "call to internal function %qE", t);
constexpr_error (loc, fundef_p,
"call to internal function %qE", t);
return false;
}
}
@ -8940,12 +9002,11 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
|| !is_std_construct_at (current_function_decl))
&& !cxx_dynamic_cast_fn_p (fun))
{
if (flags & tf_error)
{
error_at (loc, "call to non-%<constexpr%> function %qD",
fun);
explain_invalid_constexpr_fn (fun);
}
if ((flags & tf_error)
&& constexpr_error (loc, fundef_p,
"call to non-%<constexpr%> "
"function %qD", fun))
explain_invalid_constexpr_fn (fun);
return false;
}
/* A call to a non-static member function takes the address
@ -8962,8 +9023,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
constexpr substitution might not use the value. */
bool sub_now = false;
if (!potential_constant_expression_1 (x, rval, strict,
sub_now, flags,
jump_target))
sub_now, fundef_p,
flags, jump_target))
return false;
i = 1;
}
@ -8997,7 +9058,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
substitution might not use the value of the argument. */
bool sub_now = false;
if (!potential_constant_expression_1 (x, rv, strict,
sub_now, flags, jump_target))
sub_now, fundef_p, flags,
jump_target))
return false;
}
return true;
@ -9035,9 +9097,10 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (flags & tf_error)
{
tree cap = DECL_CAPTURED_VARIABLE (t);
error ("lambda capture of %qE is not a constant expression",
cap);
if (decl_constant_var_p (cap))
if (constexpr_error (input_location, fundef_p,
"lambda capture of %qE is not a "
"constant expression", cap)
&& decl_constant_var_p (cap))
inform (input_location, "because it is used as a glvalue");
}
return false;
@ -9060,8 +9123,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
&& COMPLETE_TYPE_P (TREE_TYPE (t))
&& !is_really_empty_class (TREE_TYPE (t), /*ignore_vptr*/false))
{
if (flags & tf_error)
non_const_var_error (loc, t);
if (flags & tf_error)
non_const_var_error (loc, t, fundef_p);
return false;
}
return true;
@ -9070,7 +9133,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (REINTERPRET_CAST_P (t))
{
if (flags & tf_error)
error_at (loc, "%<reinterpret_cast%> is not a constant expression");
constexpr_error (loc, fundef_p, "%<reinterpret_cast%> is not a "
"constant expression");
return false;
}
/* FALLTHRU */
@ -9092,8 +9156,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
&& !integer_zerop (from))
{
if (flags & tf_error)
error_at (loc,
"%<reinterpret_cast%> from integer to pointer");
constexpr_error (loc, fundef_p,
"%<reinterpret_cast%> from integer to "
"pointer");
return false;
}
}
@ -9165,7 +9230,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (!var_in_maybe_constexpr_fn (x))
{
if (flags & tf_error)
error_at (loc, "use of %<this%> in a constant expression");
constexpr_error (loc, fundef_p, "use of %<this%> in a "
"constant expression");
return false;
}
return true;
@ -9313,8 +9379,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
/* In C++17 lambdas can be constexpr, don't give up yet. */
return true;
else if (flags & tf_error)
error_at (loc, "lambda-expression is not a constant expression "
"before C++17");
constexpr_error (loc, fundef_p, "lambda-expression is not a "
"constant expression before C++17");
return false;
case NEW_EXPR:
@ -9325,8 +9391,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
/* In C++20, new-expressions are potentially constant. */
return true;
else if (flags & tf_error)
error_at (loc, "new-expression is not a constant expression "
"before C++20");
constexpr_error (loc, fundef_p, "new-expression is not a "
"constant expression before C++20");
return false;
case DYNAMIC_CAST_EXPR:
@ -9375,12 +9441,13 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
case AT_ENCODE_EXPR:
fail:
if (flags & tf_error)
error_at (loc, "expression %qE is not a constant expression", t);
constexpr_error (loc, fundef_p, "expression %qE is not a constant "
"expression", t);
return false;
case ASM_EXPR:
if (flags & tf_error)
inline_asm_in_constexpr_error (loc);
inline_asm_in_constexpr_error (loc, fundef_p);
return false;
case OBJ_TYPE_REF:
@ -9388,8 +9455,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
/* In C++20 virtual calls can be constexpr, don't give up yet. */
return true;
else if (flags & tf_error)
error_at (loc,
"virtual functions cannot be %<constexpr%> before C++20");
constexpr_error (loc, fundef_p, "virtual functions cannot be "
"%<constexpr%> before C++20");
return false;
case TYPEID_EXPR:
@ -9404,8 +9471,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
&& TYPE_POLYMORPHIC_P (TREE_TYPE (e)))
{
if (flags & tf_error)
error_at (loc, "%<typeid%> is not a constant expression "
"because %qE is of polymorphic type", e);
constexpr_error (loc, fundef_p, "%<typeid%> is not a "
"constant expression because %qE is "
"of polymorphic type", e);
return false;
}
return true;
@ -9465,9 +9533,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
constant expression. */
{
if (flags & tf_error)
error_at (loc,
"cast to non-integral type %qT in a constant expression",
TREE_TYPE (t));
constexpr_error (loc, fundef_p,
"cast to non-integral type %qT in a constant "
"expression", TREE_TYPE (t));
return false;
}
/* This might be a conversion from a class to a (potentially) literal
@ -9523,15 +9591,17 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (CP_DECL_THREAD_LOCAL_P (tmp) && !DECL_REALLY_EXTERN (tmp))
{
if (flags & tf_error)
error_at (DECL_SOURCE_LOCATION (tmp), "%qD defined "
"%<thread_local%> in %<constexpr%> context", tmp);
constexpr_error (DECL_SOURCE_LOCATION (tmp), fundef_p,
"%qD defined %<thread_local%> in "
"%<constexpr%> context", tmp);
return false;
}
else if (TREE_STATIC (tmp))
{
if (flags & tf_error)
error_at (DECL_SOURCE_LOCATION (tmp), "%qD defined "
"%<static%> in %<constexpr%> context", tmp);
constexpr_error (DECL_SOURCE_LOCATION (tmp), fundef_p,
"%qD defined %<static%> in %<constexpr%> "
"context", tmp);
return false;
}
else if (!check_for_uninitialized_const_var
@ -9554,9 +9624,10 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (flags & tf_error)
{
auto_diagnostic_group d;
error_at (loc, "temporary of non-literal type %qT in a "
"constant expression", TREE_TYPE (t));
explain_non_literal_class (TREE_TYPE (t));
if (constexpr_error (loc, fundef_p,
"temporary of non-literal type %qT in a "
"constant expression", TREE_TYPE (t)))
explain_non_literal_class (TREE_TYPE (t));
}
return false;
}
@ -9603,7 +9674,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (integer_zerop (denom))
{
if (flags & tf_error)
error ("division by zero is not a constant expression");
constexpr_error (input_location, fundef_p,
"division by zero is not a constant expression");
return false;
}
else
@ -9704,7 +9776,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (COND_EXPR_IS_VEC_DELETE (t) && cxx_dialect < cxx20)
{
if (flags & tf_error)
error_at (loc, "%<delete[]%> is not a constant expression");
constexpr_error (loc, fundef_p, "%<delete[]%> is not a "
"constant expression");
return false;
}
/* Fall through. */
@ -9734,7 +9807,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
{
tree this_jump_target = tmp;
if (potential_constant_expression_1 (TREE_OPERAND (t, i),
want_rval, strict, now,
want_rval, strict, now, fundef_p,
tf_none, &this_jump_target))
{
if (returns (&this_jump_target))
@ -9772,9 +9845,11 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (flags & tf_error)
{
if (TREE_CODE (t) == IF_STMT)
error_at (loc, "neither branch of %<if%> is a constant expression");
constexpr_error (loc, fundef_p, "neither branch of %<if%> is a "
"constant expression");
else
error_at (loc, "expression %qE is not a constant expression", t);
constexpr_error (loc, fundef_p, "expression %qE is not a "
"constant expression", t);
}
return false;
@ -9783,8 +9858,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
return true;
if (flags & tf_error)
{
error_at (loc, "non-constant array initialization");
diagnose_non_constexpr_vec_init (t);
if (constexpr_error (loc, fundef_p, "non-constant array "
"initialization"))
diagnose_non_constexpr_vec_init (t);
}
return false;
@ -9813,7 +9889,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
return true;
}
if (flags & tf_error)
error_at (loc, "%<goto%> is not a constant expression");
constexpr_error (loc, fundef_p, "%<goto%> is not a constant "
"expression");
return false;
}
@ -9822,8 +9899,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
if (DECL_ARTIFICIAL (t) || cxx_dialect >= cxx23)
return true;
else if (flags & tf_error)
error_at (loc, "label definition in %<constexpr%> function only "
"available with %<-std=c++2b%> or %<-std=gnu++2b%>");
constexpr_error (loc, fundef_p, "label definition in %<constexpr%> "
"function only available with %<-std=c++2b%> or "
"%<-std=gnu++2b%>");
return false;
case ANNOTATE_EXPR:
@ -9861,7 +9939,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
bool
potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
tsubst_flags_t flags)
bool fundef_p, tsubst_flags_t flags)
{
if (flags & tf_error)
{
@ -9869,13 +9947,14 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
efficiently in some cases (currently only for TRUTH_*_EXPR). If
that fails, replay the check noisily to give errors. */
flags &= ~tf_error;
if (potential_constant_expression_1 (t, want_rval, strict, now, flags))
if (potential_constant_expression_1 (t, want_rval, strict, now, fundef_p,
flags))
return true;
flags |= tf_error;
}
tree target = NULL_TREE;
return potential_constant_expression_1 (t, want_rval, strict, now,
return potential_constant_expression_1 (t, want_rval, strict, now, fundef_p,
flags, &target);
}
@ -9884,7 +9963,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
bool
potential_constant_expression (tree t)
{
return potential_constant_expression_1 (t, false, true, false, tf_none);
return potential_constant_expression_1 (t, /*want_rval*/false, /*strict*/true,
/*now*/false, /*fundef_p*/false,
tf_none);
}
/* As above, but require a constant rvalue. */
@ -9892,7 +9973,9 @@ potential_constant_expression (tree t)
bool
potential_rvalue_constant_expression (tree t)
{
return potential_constant_expression_1 (t, true, true, false, tf_none);
return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
/*now*/false, /*fundef_p*/false,
tf_none);
}
/* Like above, but complain about non-constant expressions. */
@ -9900,7 +9983,8 @@ potential_rvalue_constant_expression (tree t)
bool
require_potential_constant_expression (tree t)
{
return potential_constant_expression_1 (t, false, true, false,
return potential_constant_expression_1 (t, /*want_rval*/false, /*strict*/true,
/*now*/false, /*fundef_p*/false,
tf_warning_or_error);
}
@ -9909,7 +9993,18 @@ require_potential_constant_expression (tree t)
bool
require_potential_rvalue_constant_expression (tree t)
{
return potential_constant_expression_1 (t, true, true, false,
return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
/*now*/false, /*fundef_p*/false,
tf_warning_or_error);
}
/* Like require_potential_rvalue_constant_expression, but fundef_p is true. */
bool
require_potential_rvalue_constant_expression_fncheck (tree t)
{
return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
/*now*/false, /*fundef_p*/true,
tf_warning_or_error);
}
@ -9918,7 +10013,8 @@ require_potential_rvalue_constant_expression (tree t)
bool
require_rvalue_constant_expression (tree t)
{
return potential_constant_expression_1 (t, true, true, true,
return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
/*now*/true, /*fundef_p*/false,
tf_warning_or_error);
}
@ -9932,7 +10028,9 @@ require_rvalue_constant_expression (tree t)
bool
is_constant_expression (tree t)
{
return potential_constant_expression_1 (t, false, true, true, tf_none);
return potential_constant_expression_1 (t, /*want_rval*/false, /*strict*/true,
/*now*/true, /*fundef_p*/false,
tf_none);
}
/* As above, but expect an rvalue. */
@ -9940,7 +10038,9 @@ is_constant_expression (tree t)
bool
is_rvalue_constant_expression (tree t)
{
return potential_constant_expression_1 (t, true, true, true, tf_none);
return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
/*now*/true, /*fundef_p*/false,
tf_none);
}
/* Like above, but complain about non-constant expressions. */
@ -9948,7 +10048,8 @@ is_rvalue_constant_expression (tree t)
bool
require_constant_expression (tree t)
{
return potential_constant_expression_1 (t, false, true, true,
return potential_constant_expression_1 (t, /*want_rval*/false, /*strict*/true,
/*now*/true, /*fundef_p*/false,
tf_warning_or_error);
}
@ -9958,7 +10059,9 @@ require_constant_expression (tree t)
bool
is_static_init_expression (tree t)
{
return potential_constant_expression_1 (t, false, false, true, tf_none);
return potential_constant_expression_1 (t, /*want_rval*/false,
/*strict*/false, /*now*/true,
/*fundef_p*/false, tf_none);
}
/* Returns true if T is a potential constant expression that is not

View File

@ -8450,6 +8450,7 @@ extern bool require_potential_constant_expression (tree);
extern bool require_constant_expression (tree);
extern bool require_rvalue_constant_expression (tree);
extern bool require_potential_rvalue_constant_expression (tree);
extern bool require_potential_rvalue_constant_expression_fncheck (tree);
extern tree cxx_constant_value (tree, tree = NULL_TREE,
tsubst_flags_t = tf_error);
inline tree cxx_constant_value (tree t, tsubst_flags_t complain)

View File

@ -1332,7 +1332,7 @@ struct comp_info
&& !potential_rvalue_constant_expression (expr))
{
if (was_constexp)
require_potential_rvalue_constant_expression (expr);
require_potential_rvalue_constant_expression_fncheck (expr);
else
constexp = false;
}
@ -2670,13 +2670,17 @@ synthesized_method_walk (tree ctype, special_function_kind sfk, bool const_p,
requirements of a constexpr constructor (7.1.5), the
implicitly-defined default constructor is constexpr.
C++20:
The implicitly-defined copy/move assignment operator is constexpr if
- X is a literal type, and
- the assignment operator selected to copy/move each direct base class
subobject is a constexpr function, and
- for each non-static data member of X that is of class type (or array
thereof), the assignment operator selected to copy/move that
member is a constexpr function. */
member is a constexpr function.
C++23:
The implicitly-defined copy/move assignment operator is constexpr. */
if (constexpr_p)
*constexpr_p = (SFK_CTOR_P (sfk)
|| (SFK_ASSIGN_P (sfk) && cxx_dialect >= cxx14)

View File

@ -256,7 +256,7 @@ in the following sections.
-Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol
-Weffc++ -Wno-exceptions -Wextra-semi -Wno-inaccessible-base @gol
-Wno-inherited-variadic-ctor -Wno-init-list-lifetime @gol
-Winvalid-imported-macros @gol
-Winvalid-constexpr -Winvalid-imported-macros @gol
-Wno-invalid-offsetof -Wno-literal-suffix @gol
-Wmismatched-new-delete -Wmismatched-tags @gol
-Wmultiple-inheritance -Wnamespaces -Wnarrowing @gol
@ -3766,6 +3766,32 @@ the variable declaration statement.
@end itemize
@item -Winvalid-constexpr
@opindex Winvalid-constexpr
@opindex Wno-invalid-constexpr
Warn when a function never produces a constant expression. In C++20
and earlier, for every @code{constexpr} function and function template,
there must be at least one set of function arguments in at least one
instantiation such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression.
C++23 removed this restriction, so it's possible to have a function
or a function template marked @code{constexpr} for which no invocation
satisfies the requirements of a core constant expression.
This warning is enabled as a pedantic warning by default in C++20 and
earlier. In C++23, @option{-Winvalid-constexpr} can be turned on, in
which case it will be an ordinary warning. For example:
@smallexample
void f (int& i);
constexpr void
g (int& i)
@{
f(i); // warns by default in C++20, in C++23 only with -Winvalid-constexpr
@}
@end smallexample
@item -Winvalid-imported-macros
@opindex Winvalid-imported-macros
@opindex Wno-invalid-imported-macros

View File

@ -7,5 +7,5 @@ struct A
struct B : A
{
constexpr B(): A() { } // { dg-error "A::A" }
constexpr B(): A() { } // { dg-error "A::A" "" { target c++20_down } }
};

View File

@ -7,6 +7,6 @@ struct A {
struct B: A { };
constexpr int f(B b) { return b.i; }
struct C { C(); }; // { dg-message "" }
struct D: C { }; // { dg-message "" }
constexpr int g(D d) { return 42; } // { dg-error "invalid type" }
struct C { C(); }; // { dg-message "" "" { target c++20_down } }
struct D: C { }; // { dg-message "" "" { target c++20_down } }
constexpr int g(D d) { return 42; } // { dg-error "invalid type" "" { target c++20_down } }

View File

@ -37,7 +37,7 @@ struct base // { dg-message "no .constexpr. constructor" "" { target { !
struct derived : public base // { dg-message "base class" "" { target { ! implicit_constexpr } } }
{
constexpr derived(): base() { } // { dg-error "non-.constexpr. function" "" { target { ! implicit_constexpr } } }
constexpr derived(): base() { } // { dg-error "non-.constexpr. function" "" { target { { ! implicit_constexpr } && c++20_down } } }
};
constexpr derived obj; // { dg-error "not literal" "" { target { ! implicit_constexpr } } }

View File

@ -87,7 +87,8 @@ struct resource {
}
};
constexpr resource f(resource d)
{ return d; } // { dg-error "non-.constexpr." "" { target { ! implicit_constexpr } } }
{ return d; } // { dg-error "non-.constexpr." "" { target { { ! implicit_constexpr } && c++20_down } } }
// { dg-error "non-.constexpr." "" { target c++23 } .-2 }
constexpr resource d = f(9); // { dg-message ".constexpr." "" { target { ! implicit_constexpr } } }
// 4.4 floating-point constant expressions

View File

@ -5,7 +5,7 @@ struct A { A(); };
struct B {
friend constexpr int f(B) { return 0; } // OK
friend constexpr int f(A) { return 0; } // { dg-error "constexpr" }
friend constexpr int f(A) { return 0; } // { dg-error "constexpr" "" { target c++20_down } }
};
template <class T>

View File

@ -9,7 +9,7 @@ int g();
// We should complain about this.
template<> constexpr int A<int>::f()
{ return g(); } // { dg-error "non-.constexpr." }
{ return g(); } // { dg-error "non-.constexpr." "" { target c++20_down } }
// But not about this.
struct B

View File

@ -9,5 +9,5 @@ struct A
struct B
{
A a[1];
constexpr B() : a() {} // { dg-error "non-constant|non-.constexpr." "" { target { ! implicit_constexpr } } }
constexpr B() : a() {} // { dg-error "non-constant|non-.constexpr." "" { target { { ! implicit_constexpr } && c++20_down } } }
};

View File

@ -6,6 +6,6 @@ struct A
A(int);
};
struct B : A {}; // { dg-message "" }
struct B : A {}; // { dg-message "" "" { target c++20_down } }
constexpr int foo(B) { return 0; } // { dg-error "invalid type" }
constexpr int foo(B) { return 0; } // { dg-error "invalid type" "" { target c++20_down } }

View File

@ -13,6 +13,6 @@ constexpr X X::g(X x) { return x; }
struct Y
{
Y() { }
constexpr Y f(Y y) { return y; } // { dg-error "constexpr" "" { target { ! implicit_constexpr } } }
static constexpr Y g(Y y) { return y; } // { dg-error "constexpr" "" { target { ! implicit_constexpr } } }
constexpr Y f(Y y) { return y; } // { dg-error "constexpr" "" { target { { ! implicit_constexpr } && c++20_down } } }
static constexpr Y g(Y y) { return y; } // { dg-error "constexpr" "" { target { { ! implicit_constexpr } && c++20_down } } }
};

View File

@ -18,10 +18,10 @@ constexpr int three = one() ? 3 : nonconst_func(0);
constexpr int bogus() { return zero () ? 3 : nonconst_func(0); } // { dg-error "nonconst_func" }
// Correctly rejected (not sure why).
constexpr int correct_error() { return nonconst_func(0); } // { dg-error "nonconst_func" }
constexpr int correct_error() { return nonconst_func(0); } // { dg-error "nonconst_func" "" { target c++20_down } }
// Correctly rejected.
constexpr int z = bogus(); // { dg-error "" }
// This is also correctly rejected.
constexpr int correct_failure() { return 0 ? 3 : nonconst_func(0); } // { dg-error "nonconst_func" }
constexpr int correct_failure() { return 0 ? 3 : nonconst_func(0); } // { dg-error "nonconst_func" "" { target c++20_down } }

View File

@ -10,7 +10,7 @@ struct B {
int global; // { dg-message "not const" }
struct D : B {
constexpr D() : B(global) { } // { dg-error "global|argument" }
constexpr D() : B(global) { } // { dg-error "global|argument" "" { target c++20_down } }
};
struct A2 {

View File

@ -17,7 +17,7 @@ public:
constexpr static Inner & getInner()
/* I am surprised this is considered a constexpr */
{
return *((Inner *)4); // { dg-error "reinterpret_cast" }
return *((Inner *)4); // { dg-error "reinterpret_cast" "" { target c++20_down } }
}
};

View File

@ -14,5 +14,5 @@ foo ()
constexpr volatile int // { dg-warning "deprecated" "" { target c++2a } }
bar ()
{
return i; // { dg-error "lvalue-to-rvalue conversion of a volatile lvalue" }
return i; // { dg-error "lvalue-to-rvalue conversion of a volatile lvalue" "" { target c++20_down } }
}

View File

@ -5,7 +5,7 @@ void g();
void h();
constexpr void f(int* p, int* q) {
if (p != q && *p < 0) // { dg-error "neither branch of 'if' is a constant expression" }
if (p != q && *p < 0) // { dg-error "neither branch of 'if' is a constant expression" "" { target c++20_down } }
g();
else
h();

View File

@ -10,7 +10,7 @@ struct B {
int *c = &x->a;
while (*c)
c = reinterpret_cast<int *>((reinterpret_cast<char *>(c) + *c));
*c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c); // { dg-error "reinterpret_cast" }
*c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c); // { dg-error "reinterpret_cast" "" { target c++20_down } }
}
};
struct C : A {

View File

@ -10,7 +10,7 @@ struct B {
int *c = &x->a;
while (*c)
c = reinterpret_cast<int *>((reinterpret_cast<char *>(c) + *c));
*c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c); // { dg-error "reinterpret_cast" }
*c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c); // { dg-error "reinterpret_cast" "" { target c++20_down } }
}
};
struct C : A {

View File

@ -11,7 +11,7 @@ foo (int x)
case 2:
break;
}
throw 42; // { dg-error "is not a constant expression" }
throw 42; // { dg-error "is not a constant expression" "" { target c++20_down } }
return 0;
}
@ -29,7 +29,7 @@ bar (int x)
continue;
break;
}
throw -42; // { dg-error "is not a constant expression" }
throw -42; // { dg-error "is not a constant expression" "" { target c++20_down } }
}
while (0);
return x;

View File

@ -3,7 +3,7 @@
struct A { A(); };
constexpr int f(int i) {
static int j = i; // { dg-error "static" }
static int j = i; // { dg-error "static" "" { target c++20_down } }
thread_local int l = i; // { dg-error "thread_local" "" { target c++20_down } }
goto foo; // { dg-error "goto" "" { target c++20_down } }
foo:

View File

@ -21,7 +21,7 @@ bar()
A a = foo();
a.p->n = 5;
return a;
} // { dg-error "non-.constexpr." }
} // { dg-error "non-.constexpr." "" { target c++20_down } }
constexpr int
baz()

View File

@ -7,18 +7,18 @@ constexpr void f1() {
constexpr void f2() {
if (true)
throw; // { dg-error "not a constant expression" }
throw; // { dg-error "not a constant expression" "" { target c++20_down } }
}
constexpr void f3() {
if (false)
;
else
throw; // { dg-error "not a constant expression" }
throw; // { dg-error "not a constant expression" "" { target c++20_down } }
}
constexpr void f4() {
throw; // { dg-error "not a constant expression" }
throw; // { dg-error "not a constant expression" "" { target c++20_down } }
}
constexpr int fun(int n) {

View File

@ -0,0 +1,96 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++23 } }
// { dg-options "-Winvalid-constexpr -pedantic-errors" }
// No constexpr constructors = not a literal type.
struct NonLiteral {
NonLiteral() {}
};
// C++23: It is possible to write a constexpr function for which no
// invocation satisfies the requirements of a core constant expression.
constexpr NonLiteral
fn0 (int) // { dg-warning "invalid return type" }
{
return NonLiteral{};
}
constexpr int
fn1 (NonLiteral) // { dg-warning "invalid type" }
{
return 42;
}
// From P2448.
void f(int& i) {
i = 0;
}
constexpr void g(int& i) {
f(i); // { dg-warning "call to" }
}
// [dcl.constexpr] used to have this.
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
struct B {
constexpr B(int) : i(0) { }
int i;
};
int global;
struct D : B {
constexpr D() : B(global) { } // { dg-warning "not usable" }
// ill-formed, no diagnostic required
// lvalue-to-rvalue conversion on non-constant global
};
// If no specialization of the template would satisfy the requirements
// for a constexpr function when considered as a non-template function,
// the template is ill-formed, no diagnostic required.
template<typename>
constexpr void
fn2 ()
{
int i = 42;
f (i);
}
void
fn3 ()
{
fn2<int>();
}
constexpr volatile int cvi = 10;
constexpr int
fn4 ()
{
return cvi; // { dg-warning "lvalue-to-rvalue conversion" }
}
constexpr unsigned int
fn5 (int *p)
{
unsigned int *q = reinterpret_cast<unsigned int *>(p); // { dg-warning "reinterpret_cast" }
return *q;
}
constexpr int
fn6 (int i)
{
void *p = (void *) 1LL; // { dg-warning ".reinterpret_cast. from integer to pointer" }
return 42;
}
constexpr int
fn7 (int i)
{
static int s = i; // { dg-warning "static" }
return s;
}

View File

@ -0,0 +1,53 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++23 } }
// { dg-options "-Winvalid-constexpr -pedantic-errors" }
// [dcl.constexpr]/4 used to say:
// The definition of a constexpr constructor whose function-body
// is not = delete shall additionally satisfy the following requirements:
// (4.1) for a non-delegating constructor, every constructor selected to initialize non-static data members and base class subobjects shall be a constexpr constructor;
// (4.2) for a delegating constructor, the target constructor shall be a constexpr constructor.
// This continues to be OK.
struct Length {
constexpr explicit Length(int i = 0) : val(i) { }
private:
int val;
};
struct X {
X() {}
X(int i_) : i(i_) {}
int i;
};
struct S {
X x;
// Calls a non-constexpr constructor X::X(int).
constexpr S(int i) : x(i) { } // { dg-warning "call to" }
S(int, int) { }
// Target constructor isn't constexpr.
constexpr S() : S(42, 42) { } // { dg-warning "call to" }
};
namespace N1 {
struct X {
void x();
};
struct Y {
X x;
constexpr void y() { x.x(); } // { dg-warning "call to" }
};
}
void g();
struct A {
constexpr A() { g(); } // { dg-warning "call to" }
};
struct B {
constexpr B& operator=(const B&) { g(); return *this; } // { dg-warning "call to" }
constexpr B& operator=(B&&) { g(); return *this; } // { dg-warning "call to" }
};

View File

@ -0,0 +1,24 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++23 } }
// Test that we get a diagnostic even in C++23 if you do call the function.
constexpr unsigned int
fn0 (const int *p)
{
return *reinterpret_cast<unsigned const int *>(p); // { dg-error ".reinterpret_cast. is not a constant expression" }
}
constexpr void *
fn1 (int i)
{
return (void *) 1LL; // { dg-error ".reinterpret_cast." }
}
void
g ()
{
constexpr int i = 42;
constexpr auto a1 = fn0 (&i);
constexpr auto a2 = fn1 (i); // { dg-error "called in a constant expression" }
}

View File

@ -0,0 +1,14 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++23 } }
// { dg-options "-Winvalid-constexpr" }
constexpr volatile int i = 10;
constexpr int
bar ()
{
return i; // { dg-warning "lvalue-to-rvalue conversion of a volatile lvalue" }
}
constexpr int x = bar (); // { dg-error "called in a constant expression" }

View File

@ -0,0 +1,26 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++20 } }
// { dg-options "" }
// The definition of a constexpr destructor whose function-body is not
// =delete shall additionally satisfy the following requirement:
// (5.1) for every subobject of class type or (possibly multi-dimensional)
// array thereof, that class type shall have a constexpr destructor.
struct B {
B() { }
~B() { }
};
struct T : B {
constexpr ~T() { } // { dg-warning "call to" "" { target c++20_down } }
};
struct S {
constexpr S() = default; // was error: implicit S() is not constexpr, now OK
~S() noexcept(false) = default; // OK, despite mismatched exception specification
private:
int i;
S(S&); // OK: private copy constructor
};
S::S(S&) = default; // OK: defines copy constructor

View File

@ -0,0 +1,35 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++23 } }
// { dg-options "-Winvalid-constexpr" }
// A copy/move assignment operator for a class X that is defaulted and
// not defined as deleted is implicitly defined when it is odr-used,
// when it is needed for constant evaluation, or when it is explicitly
// defaulted after its first declaration.
// The implicitly-defined copy/move assignment operator is constexpr.
struct S {
constexpr S() {}
S& operator=(const S&) = default;
S& operator=(S&&) = default;
};
struct U {
constexpr U& operator=(const U&) = default;
constexpr U& operator=(U&&) = default;
};
constexpr void
g ()
{
S a;
S b;
b = a;
b = S{};
U u, v;
u = v;
u = U{};
}
static_assert ((g(), true), "");

View File

@ -0,0 +1,23 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++20 } }
// { dg-options "" }
template <typename T>
struct Wrapper {
constexpr Wrapper() = default;
constexpr Wrapper(Wrapper const&) = default;
constexpr Wrapper(T const& t) : t(t) { }
constexpr T get() const { return t; }
constexpr bool operator==(Wrapper const&) const = default; // { dg-warning "call to" "" { target c++20_down } }
private:
T t;
};
struct X {
X();
bool operator==(X const&) const;
};
Wrapper<X> x;

View File

@ -5,6 +5,6 @@ constexpr int
foo ()
{
goto lab; // { dg-error "'goto' in 'constexpr' function only available with" "" { target c++20_down } }
lab: // { dg-error "'goto' is not a constant expression" "" { target { c++23 } } .-1 }
lab:
return 1;
}

View File

@ -18,8 +18,19 @@ bar ()
}
constexpr int
baz (int x)
baz ()
{
thread_local int a; // { dg-error "'a' defined 'thread_local' in 'constexpr' context" }
return ++a;
}
// In C++23, we get errors about the non-constant expressions only if we
// actually call the functions in a constexpr context.
void
test ()
{
constexpr int a = foo (); // { dg-error "constant expression" }
constexpr int b = bar (); // { dg-error "constant expression" }
constexpr int c = baz (); // { dg-error "constant expression" }
}

View File

@ -0,0 +1,96 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++14 } }
// { dg-options "" }
// No constexpr constructors = not a literal type.
struct NonLiteral {
NonLiteral() {}
};
// C++23: It is possible to write a constexpr function for which no
// invocation satisfies the requirements of a core constant expression.
constexpr NonLiteral
fn0 (int) // { dg-warning "invalid return type" "" { target c++20_down } }
{
return NonLiteral{};
}
constexpr int
fn1 (NonLiteral) // { dg-warning "invalid type" "" { target c++20_down } }
{
return 42;
}
// From P2448.
void f(int& i) {
i = 0;
}
constexpr void g(int& i) {
f(i); // { dg-warning "call to" "" { target c++20_down } }
}
// [dcl.constexpr] used to have this.
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
struct B {
constexpr B(int) : i(0) { }
int i;
};
int global;
struct D : B {
constexpr D() : B(global) { } // { dg-warning "not usable" "" { target c++20_down } }
// ill-formed, no diagnostic required
// lvalue-to-rvalue conversion on non-constant global
};
// If no specialization of the template would satisfy the requirements
// for a constexpr function when considered as a non-template function,
// the template is ill-formed, no diagnostic required.
template<typename>
constexpr void
fn2 ()
{
int i = 42;
f (i);
}
void
fn3 ()
{
fn2<int>();
}
constexpr volatile int cvi = 10;
constexpr int
fn4 ()
{
return cvi; // { dg-warning "lvalue-to-rvalue conversion" "" { target c++20_down } }
}
constexpr unsigned int
fn5 (int *p)
{
unsigned int *q = reinterpret_cast<unsigned int *>(p); // { dg-warning "reinterpret_cast" "" { target c++20_down } }
return *q;
}
constexpr int
fn6 (int i)
{
void *p = (void *) 1LL; // { dg-warning ".reinterpret_cast. from integer to pointer" "" { target c++20_down } }
return 42;
}
constexpr int
fn7 (int i)
{
static int s = i; // { dg-error "static" "" { target c++20_down } }
return s;
}

View File

@ -0,0 +1,53 @@
// PR c++/106649
// P2448 - Relaxing some constexpr restrictions
// { dg-do compile { target c++14 } }
// { dg-options "" }
// [dcl.constexpr]/4 used to say:
// The definition of a constexpr constructor whose function-body
// is not = delete shall additionally satisfy the following requirements:
// (4.1) for a non-delegating constructor, every constructor selected to initialize non-static data members and base class subobjects shall be a constexpr constructor;
// (4.2) for a delegating constructor, the target constructor shall be a constexpr constructor.
// This continues to be OK.
struct Length {
constexpr explicit Length(int i = 0) : val(i) { }
private:
int val;
};
struct X {
X() {}
X(int i_) : i(i_) {}
int i;
};
struct S {
X x;
// Calls a non-constexpr constructor X::X(int).
constexpr S(int i) : x(i) { } // { dg-warning "call to" "" { target c++20_down } }
S(int, int) { }
// Target constructor isn't constexpr.
constexpr S() : S(42, 42) { } // { dg-warning "call to" "" { target c++20_down } }
};
namespace N1 {
struct X {
void x();
};
struct Y {
X x;
constexpr void y() { x.x(); } // { dg-warning "call to" "" { target c++20_down } }
};
}
void g();
struct A {
constexpr A() { g(); } // { dg-warning "call to" "" { target c++20_down } }
};
struct B {
constexpr B& operator=(const B&) { g(); return *this; } // { dg-warning "call to" "" { target c++20_down } }
constexpr B& operator=(B&&) { g(); return *this; } // { dg-warning "call to" "" { target c++20_down } }
};

View File

@ -134,8 +134,8 @@
#ifndef __cpp_constexpr
# error "__cpp_constexpr"
#elif __cpp_constexpr != 202110
# error "__cpp_constexpr != 202110"
#elif __cpp_constexpr != 202207
# error "__cpp_constexpr != 202207"
#endif
#ifndef __cpp_decltype_auto

View File

@ -57,7 +57,6 @@ consteval int
f13 (int x)
{
static int a = 5; // { dg-error "'a' defined 'static' in 'consteval' function only available with" "" { target c++20_only } }
// { dg-error "'a' defined 'static' in 'constexpr' context" "" { target c++23 } .-1 }
thread_local int b = 6; // { dg-error "'b' defined 'thread_local' in 'consteval' function only available with" "" { target c++20_only } }
return x;
}

View File

@ -13,7 +13,7 @@ void *operator new (std::size_t) noexcept;
constexpr bool
foo ()
{
auto p = static_cast<int *> (::operator new (sizeof (int))); // { dg-error "call to non-'constexpr' function" }
auto p = static_cast<int *> (::operator new (sizeof (int))); // { dg-error "call to non-'constexpr' function" "" { target c++20_down } }
*p = 1;
::operator delete (p);
return false;
@ -24,7 +24,7 @@ struct S { constexpr S () : s (0) {} int s; };
constexpr bool
bar ()
{
auto p = static_cast<S *> (::operator new (sizeof (S))); // { dg-error "call to non-'constexpr' function" }
auto p = static_cast<S *> (::operator new (sizeof (S))); // { dg-error "call to non-'constexpr' function" "" { target c++20_down } }
auto q = new (p) S ();
q->s++;
q->~S ();

View File

@ -6,7 +6,6 @@ constexpr int foo ()
try { // { dg-warning "function-try-block body of 'constexpr' function only available with" "" { target c++17_down } }
int a; // { dg-error "uninitialized variable 'a' in 'constexpr' function" "" { target c++17_down } }
static double b = 1.0;// { dg-error "'b' defined 'static' in 'constexpr' function only available with" "" { target c++20_down } }
// { dg-error "'b' defined 'static' in 'constexpr' context" "" { target c++23 } .-1 }
goto l; // { dg-error "'goto' in 'constexpr' function only available with" "" { target c++20_down } }
l:;
return 0;
@ -22,7 +21,6 @@ constexpr int bar ()
{
int a; // { dg-error "uninitialized variable 'a' in 'constexpr' function" "" { target c++17_down } }
static long double b = 3.0;// { dg-error "'b' defined 'static' in 'constexpr' function only available with" "" { target c++20_down } }
// { dg-error "'b' defined 'static' in 'constexpr' context" "" { target c++23 } .-1 }
goto l; // { dg-error "'goto' in 'constexpr' function only available with" "" { target c++20_down } }
l:;
try { // { dg-warning "'try' in 'constexpr' function only available with" "" { target c++17_down } }

View File

@ -9,7 +9,7 @@ struct A
struct B
{
A a;
bool operator==(const B&) const = default; // { dg-error "A::operator==" "" { target { ! implicit_constexpr } } }
bool operator==(const B&) const = default; // { dg-error "A::operator==" "" { target { { ! implicit_constexpr } && c++20_down } } }
};
constexpr bool x = B() == B(); // { dg-error "non-.constexpr" "" { target { ! implicit_constexpr } } }

View File

@ -7,8 +7,8 @@ struct A {
struct D
{
A i;
bool operator==(const D& x) const = default; // { dg-error "A::operator==" }
bool operator!=(const D& z) const = default; // { dg-error "D::operator==" }
bool operator==(const D& x) const = default; // { dg-error "A::operator==" "" { target c++20_down } }
bool operator!=(const D& z) const = default; // { dg-error "D::operator==" "" { target c++20_down } }
};
constexpr D d{A()};

View File

@ -1,7 +1,5 @@
// { dg-do compile { target c++11 } }
constexpr int foo() { thread_local int i __attribute__((unused)) {}; return 1; } // { dg-error "40:.i. defined .thread_local." "" { target c++20_down } }
// { dg-error "40:.i. defined .thread_local. in .constexpr. context" "" { target c++23 } .-1 }
constexpr int bar() { static int i __attribute__((unused)) {}; return 1; } // { dg-error "34:.i. defined .static." "" { target c++20_down } }
// { dg-error "34:.i. defined .static. in .constexpr. context" "" { target c++23 } .-1 }

View File

@ -1,6 +1,6 @@
// PR c++/79664
// { dg-do compile }
// { dg-options "-std=c++14 -fopenmp" }
// { dg-options "-std=c++14 -fopenmp -Winvalid-constexpr -pedantic-errors" }
constexpr int
f1 ()

View File

@ -1,7 +1,7 @@
// Verify that -fsanitize=vptr downcast instrumentation works properly
// inside of constexpr.
// { dg-do compile }
// { dg-options "-std=c++11 -fsanitize=vptr" }
// { dg-options "-std=c++11 -fsanitize=vptr -Winvalid-constexpr -pedantic-errors" }
struct S {
constexpr S() : a(0) {}