C++ PATCH to implement C++20 P1143R2, constinit (PR c++/91360)

Jason Merrill jason@redhat.com
Fri Aug 23 23:13:00 GMT 2019


On 8/14/19 2:22 PM, Marek Polacek wrote:
> This patch implements the C++20 specifier constinit, as described in
> <http://wg21.link/p1143r2>.  It makes sure that the compiler requires constant
> initialization of a variable (it can only be applied to variables with static
> or thread storage duration).  Note the variable is *not* const; it is posssible
> to modify it even after initialization has taken place.
> 
> The main bits are in store_init_value.  The c-family/ and parser.c bits
> handle parsing this new decl-specifier.  decl.c is sprinkled with various
> checks, e.g. you can't have a parameter declared "constinit".
> 
> It was also needed to add the -Wc++20-compat option to warn when "constinit"
> was used as an identifier.  Note that I am a bit presumptuous about C++20 here,
> perhaps I should be only adding -Wc++2a-compat at this point.

I think it's a good bet at this point.  We can always remove the flag if 
somehow it doesn't work out.

> As an extension, I'm also adding the __constinit keyword, so that you can
> use this feature in C++17 and lesser, effectively supplanting the clang
> require_constant_initialization attribute.
> 
> I spent significant time writing the tests (I stol^Wborrowed a couple of
> reference-related tests from clang, I'll cop to that).

Yay free software!

> Here's an example of the new diagnostic.  For
> 
>    int nonconst;
>    constinit int i = nonconst;
> 
> we now issue:
> 
> q.C:2:15: error: ‘constinit’ variable ‘i’ does not have a constant initializer
>      2 | constinit int i = nonconst;
>        |               ^
> q.C:2:19: error: the value of ‘nonconst’ is not usable in a constant expression
>      2 | constinit int i = nonconst;
>        |                   ^~~~~~~~
> q.C:1:5: note: ‘int nonconst’ is not const
>      1 | int nonconst;
>        |     ^~~~~~~~
> 
> I'm not crazy about the two errors, but I felt that revamping the diagnostics
> would be out of scope for this patch.
> 
> Bootstrapped/regtested on x86_64-linux, ok for trunk?
> 
> 2019-08-14  Marek Polacek  <polacek@redhat.com>
> 
> 	PR c++/91360 - Implement C++20 P1143R2: constinit.
> 	* c-common.c (c_common_reswords): Add constinit and __constinit.
> 	(keyword_is_decl_specifier): Handle RID_CONSTINIT.
> 	* c-common.h (enum rid): Add RID_CONSTINIT, RID_FIRST_CXX20, and
> 	RID_LAST_CXX20.
> 	(D_CXX20): Define.
> 	* c-cppbuiltin.c (c_cpp_builtins): Define __cpp_constinit.
> 	* c-format.c (cxx_keywords): Add "constinit".
> 	* c.opt (Wc++2a-compat, Wc++20-compat): New options.
> 
> 	* cp-tree.h (DECL_DECLARED_CONSTINIT_P): Define.
> 	(enum cp_decl_spec): Add ds_constinit.
> 	* decl.c (duplicate_decls): Set DECL_DECLARED_CONSTINIT_P.
> 	(check_tag_decl): Give an error for constinit in type declarations.
> 	(check_initializer): Also check DECL_DECLARED_CONSTINIT_P.
> 	(cp_finish_decl): Add checking for a constinit declaration.
> 	(grokdeclarator): Add checking for a declaration with the constinit
> 	specifier.  Set DECL_DECLARED_CONSTINIT_P for VAR_Ps.
> 	* lex.c (init_reswords): Handle D_CXX20.
> 	* parser.c (cp_lexer_get_preprocessor_token): Pass a better location
> 	to warning_at.  Warn about C++20 keywords.
> 	(cp_keyword_starts_decl_specifier_p): Handle RID_CONSTINIT.
> 	(cp_parser_diagnose_invalid_type_name): Add an inform about constinit.
> 	(cp_parser_decl_specifier_seq): Handle RID_CONSTINIT.
> 	(set_and_check_decl_spec_loc): Add "constinit".
> 	* typeck2.c (store_init_value): If a constinit variable wasn't
> 	initialized using a constant initializer, give an error.
> 
> 	* doc/invoke.texi: Document -Wc++20-compat.
> 
> 	* g++.dg/cpp2a/constinit1.C: New test.
> 	* g++.dg/cpp2a/constinit2.C: New test.
> 	* g++.dg/cpp2a/constinit3.C: New test.
> 	* g++.dg/cpp2a/constinit4.C: New test.
> 	* g++.dg/cpp2a/constinit5.C: New test.
> 	* g++.dg/cpp2a/constinit6.C: New test.
> 	* g++.dg/cpp2a/constinit7.C: New test.
> 	* g++.dg/cpp2a/constinit8.C: New test.
> 	* g++.dg/cpp2a/constinit9.C: New test.
> 	* g++.dg/cpp2a/constinit10.C: New test.
> 	* g++.dg/cpp2a/constinit11.C: New test.
> 	* g++.dg/cpp2a/constinit12.C: New test.
> 
> diff --git gcc/c-family/c-common.c gcc/c-family/c-common.c
> index 610cb905814..eb0f1ba9993 100644
> --- gcc/c-family/c-common.c
> +++ gcc/c-family/c-common.c
> @@ -326,8 +326,9 @@ static bool nonnull_check_p (tree, unsigned HOST_WIDE_INT);
>      C --std=c89: D_C99 | D_CXXONLY | D_OBJC | D_CXX_OBJC
>      C --std=c99: D_CXXONLY | D_OBJC
>      ObjC is like C except that D_OBJC and D_CXX_OBJC are not set
> -   C++ --std=c++98: D_CONLY | D_CXX11 | D_OBJC
> -   C++ --std=c++11: D_CONLY | D_OBJC
> +   C++ --std=c++98: D_CONLY | D_CXX11 | D_CXX20 | D_OBJC
> +   C++ --std=c++11: D_CONLY | D_CXX20 | D_OBJC
> +   C++ --std=c++2a: D_CONLY | D_OBJC
>      ObjC++ is like C++ except that D_OBJC is not set
>   
>      If -fno-asm is used, D_ASM is added to the mask.  If
> @@ -392,6 +393,7 @@ const struct c_common_resword c_common_reswords[] =
>     { "__complex__",	RID_COMPLEX,	0 },
>     { "__const",		RID_CONST,	0 },
>     { "__const__",	RID_CONST,	0 },
> +  { "__constinit",	RID_CONSTINIT,	D_CXXONLY },
>     { "__decltype",       RID_DECLTYPE,   D_CXXONLY },
>     { "__direct_bases",   RID_DIRECT_BASES, D_CXXONLY },
>     { "__extension__",	RID_EXTENSION,	0 },
> @@ -462,6 +464,7 @@ const struct c_common_resword c_common_reswords[] =
>     { "class",		RID_CLASS,	D_CXX_OBJC | D_CXXWARN },
>     { "const",		RID_CONST,	0 },
>     { "constexpr",	RID_CONSTEXPR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
> +  { "constinit",	RID_CONSTINIT,	D_CXXONLY | D_CXX20 | D_CXXWARN },
>     { "const_cast",	RID_CONSTCAST,	D_CXXONLY | D_CXXWARN },
>     { "continue",		RID_CONTINUE,	0 },
>     { "decltype",         RID_DECLTYPE,   D_CXXONLY | D_CXX11 | D_CXXWARN },
> @@ -7912,6 +7915,7 @@ keyword_is_decl_specifier (enum rid keyword)
>       case RID_TYPEDEF:
>       case RID_FRIEND:
>       case RID_CONSTEXPR:
> +    case RID_CONSTINIT:
>         return true;
>       default:
>         return false;
> diff --git gcc/c-family/c-common.h gcc/c-family/c-common.h
> index 117d729091a..17bd7b1c7d8 100644
> --- gcc/c-family/c-common.h
> +++ gcc/c-family/c-common.h
> @@ -180,6 +180,9 @@ enum rid
>     /* C++11 */
>     RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
>   
> +  /* C++20 */
> +  RID_CONSTINIT,
> +
>     /* char8_t */
>     RID_CHAR8,
>   
> @@ -250,6 +253,8 @@ enum rid
>   
>     RID_FIRST_CXX11 = RID_CONSTEXPR,
>     RID_LAST_CXX11 = RID_STATIC_ASSERT,
> +  RID_FIRST_CXX20 = RID_CONSTINIT,
> +  RID_LAST_CXX20 = RID_CONSTINIT,
>     RID_FIRST_AT = RID_AT_ENCODE,
>     RID_LAST_AT = RID_AT_IMPLEMENTATION,
>     RID_FIRST_PQ = RID_IN,
> @@ -427,6 +432,7 @@ extern machine_mode c_default_pointer_mode;
>   #define D_CXX_CONCEPTS  0x0400	/* In C++, only with concepts.  */
>   #define D_TRANSMEM	0X0800	/* C++ transactional memory TS.  */
>   #define D_CXX_CHAR8_T	0X1000	/* In C++, only with -fchar8_t.  */
> +#define D_CXX20		0x2000  /* In C++, C++20 only.  */
>   
>   #define D_CXX_CONCEPTS_FLAGS D_CXXONLY | D_CXX_CONCEPTS
>   #define D_CXX_CHAR8_T_FLAGS D_CXXONLY | D_CXX_CHAR8_T
> diff --git gcc/c-family/c-cppbuiltin.c gcc/c-family/c-cppbuiltin.c
> index d389f8ca4a0..a08b5918209 100644
> --- gcc/c-family/c-cppbuiltin.c
> +++ gcc/c-family/c-cppbuiltin.c
> @@ -986,6 +986,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	{
>   	  /* Set feature test macros for C++2a.  */
>   	  cpp_define (pfile, "__cpp_conditional_explicit=201806");
> +	  cpp_define (pfile, "__cpp_constinit=201907");
>   	  cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806");
>   	  cpp_define (pfile, "__cpp_impl_destroying_delete=201806");
>   	}
> diff --git gcc/c-family/c-format.c gcc/c-family/c-format.c
> index 6b059969e67..91bae3d6096 100644
> --- gcc/c-family/c-format.c
> +++ gcc/c-family/c-format.c
> @@ -2958,6 +2958,7 @@ static const token_t cxx_keywords[] =
>      NAME ("catch", NULL),
>      NAME ("constexpr if", NULL),
>      NAME ("constexpr", NULL),
> +   NAME ("constinit", NULL),
>      NAME ("consteval", NULL),
>      NAME ("decltype", NULL),
>      NAME ("nullptr", NULL),
> diff --git gcc/c-family/c.opt gcc/c-family/c.opt
> index 257cadfa5f1..4c468d0f6c2 100644
> --- gcc/c-family/c.opt
> +++ gcc/c-family/c.opt
> @@ -400,6 +400,13 @@ Wc++17-compat
>   C++ ObjC++ Var(warn_cxx17_compat) Warning LangEnabledBy(C++ ObjC++,Wall)
>   Warn about C++ constructs whose meaning differs between ISO C++ 2014 and ISO C++ 2017.
>   
> +Wc++2a-compat
> +C++ ObjC++ Warning Alias(Wc++20-compat) Undocumented
> +
> +Wc++20-compat
> +C++ ObjC++ Var(warn_cxx20_compat) Warning LangEnabledBy(C++ ObjC++,Wall)
> +Warn about C++ constructs whose meaning differs between ISO C++ 2017 and ISO C++ 2020.
> +
>   Wcast-function-type
>   C ObjC C++ ObjC++ Var(warn_cast_function_type) Warning EnabledBy(Wextra)
>   Warn about casts between incompatible function types.
> diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h
> index bdb7778c04b..4b4070fdb8e 100644
> --- gcc/cp/cp-tree.h
> +++ gcc/cp/cp-tree.h
> @@ -489,6 +489,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>         DECL_MUTABLE_P (in FIELD_DECL)
>         DECL_DEPENDENT_P (in USING_DECL)
>         LABEL_DECL_BREAK (in LABEL_DECL)
> +      DECL_DECLARED_CONSTINIT_P (in VAR_DECL)
>      1: C_TYPEDEF_EXPLICITLY_SIGNED (in TYPE_DECL).
>         DECL_TEMPLATE_INSTANTIATED (in a VAR_DECL or a FUNCTION_DECL)
>         DECL_MEMBER_TEMPLATE_P (in TEMPLATE_DECL)
> @@ -3162,6 +3163,10 @@ struct GTY(()) lang_decl {
>   #define DECL_DECLARED_CONSTEXPR_P(DECL) \
>     DECL_LANG_FLAG_8 (VAR_OR_FUNCTION_DECL_CHECK (STRIP_TEMPLATE (DECL)))
>   
> +/* True if DECL is declared 'constinit'.  */
> +#define DECL_DECLARED_CONSTINIT_P(DECL) \
> +  DECL_LANG_FLAG_0 (VAR_DECL_CHECK (STRIP_TEMPLATE (DECL)))

Hmm, given that 'constinit' only affects the declaration, do we really 
need a flag on the VAR_DECL?

>   // True if NODE was declared as 'concept'.  The flag implies that the
>   // declaration is constexpr, that the declaration cannot be specialized or
>   // refined, and that the result type must be convertible to bool.
> @@ -5815,6 +5820,7 @@ enum cp_decl_spec {
>     ds_alias,
>     ds_constexpr,
>     ds_complex,
> +  ds_constinit,
>     ds_thread,
>     ds_type_spec,
>     ds_redefined_builtin_type_spec,
> diff --git gcc/cp/decl.c gcc/cp/decl.c
> index ff3b90dba54..943f338696c 100644
> --- gcc/cp/decl.c
> +++ gcc/cp/decl.c
> @@ -2205,8 +2205,12 @@ duplicate_decls (tree newdecl, tree olddecl, bool newdecl_is_friend)
>   	  DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (newdecl)
>   	    |= DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (olddecl);
>             if (DECL_CLASS_SCOPE_P (olddecl))
> -            DECL_DECLARED_CONSTEXPR_P (newdecl)
> -	      |= DECL_DECLARED_CONSTEXPR_P (olddecl);
> +	    {
> +	      DECL_DECLARED_CONSTEXPR_P (newdecl)
> +		|= DECL_DECLARED_CONSTEXPR_P (olddecl);
> +	      DECL_DECLARED_CONSTINIT_P (newdecl)
> +		|= DECL_DECLARED_CONSTINIT_P (olddecl);
> +	    }

Hmm, the existing code limiting the unification of constexpr to 
class-scope variables seems wrong:

constexpr float pi = 3.14;
extern const float pi;
constexpr float x = pi; // should be OK

>   
>   	  /* Merge the threadprivate attribute from OLDDECL into NEWDECL.  */
>   	  if (DECL_LANG_SPECIFIC (olddecl)
> @@ -4963,6 +4967,9 @@ check_tag_decl (cp_decl_specifier_seq *declspecs,
>         else if (decl_spec_seq_has_spec_p (declspecs,  ds_constexpr))
>           error_at (declspecs->locations[ds_constexpr],
>   		  "%<constexpr%> cannot be used for type declarations");
> +      else if (decl_spec_seq_has_spec_p (declspecs,  ds_constinit))
> +	error_at (declspecs->locations[ds_constinit],
> +		  "%<constinit%> cannot be used for type declarations");
>       }
>   
>     if (declspecs->attributes && warn_attributes && declared_type)
> @@ -6596,11 +6603,12 @@ check_initializer (tree decl, tree init, int flags, vec<tree, va_gc> **cleanups)
>   		 about aggregate initialization of non-aggregate classes.  */
>   	      flags |= LOOKUP_ALREADY_DIGESTED;
>   	    }
> -	  else if (DECL_DECLARED_CONSTEXPR_P (decl))
> +	  else if (DECL_DECLARED_CONSTEXPR_P (decl)
> +		   || DECL_DECLARED_CONSTINIT_P (decl))
>   	    {
> -	      /* Declared constexpr, but no suitable initializer; massage
> -		 init appropriately so we can pass it into store_init_value
> -		 for the error.  */
> +	      /* Declared constexpr or constinit, but no suitable initializer;
> +		 massage init appropriately so we can pass it into
> +		 store_init_value for the error.  */
>   	      if (CLASS_TYPE_P (type)
>   		  && (!init || TREE_CODE (init) == TREE_LIST))
>   		{
> @@ -7254,6 +7262,18 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p,
>   
>     if (VAR_P (decl))
>       {
> +      duration_kind dk = decl_storage_duration (decl);
> +      /* [dcl.constinit]/1 "The constinit specifier shall be applied
> +	 only to a declaration of a variable with static or thread storage
> +	 duration."  */
> +      if (DECL_DECLARED_CONSTINIT_P (decl)
> +	  && !(dk == dk_thread || dk == dk_static))
> +	{
> +	  error ("%<constinit%> can only be applied to a variable with static "
> +		 "or thread storage duration");
> +	  return;
> +	}
> +
>         /* If this is a local variable that will need a mangled name,
>   	 register it now.  We must do this before processing the
>   	 initializer for the variable, since the initialization might
> @@ -10478,6 +10498,7 @@ grokdeclarator (const cp_declarator *declarator,
>     bool template_parm_flag = false;
>     bool typedef_p = decl_spec_seq_has_spec_p (declspecs, ds_typedef);
>     bool constexpr_p = decl_spec_seq_has_spec_p (declspecs, ds_constexpr);
> +  bool constinit_p = decl_spec_seq_has_spec_p (declspecs, ds_constinit);
>     bool late_return_type_p = false;
>     bool array_parameter_p = false;
>     location_t saved_loc = input_location;
> @@ -10764,6 +10785,24 @@ grokdeclarator (const cp_declarator *declarator,
>         return error_mark_node;
>       }
>   
> +  if (constinit_p && typedef_p)
> +    {
> +      error_at (declspecs->locations[ds_constinit],
> +		"%<constinit%> cannot appear in a typedef declaration");
> +      return error_mark_node;
> +    }
> +
> +  /* [dcl.spec]/2 "At most one of the constexpr, consteval, and constinit
> +     keywords shall appear in a decl-specifier-seq."  */
> +  if (constinit_p && constexpr_p)
> +    {
> +      error_at (min_location (declspecs->locations[ds_constinit],
> +			      declspecs->locations[ds_constexpr]),
> +		"can use at most one of the %<constinit%> and %<constexpr%> "
> +		"specifiers");
> +      return error_mark_node;
> +    }
> +
>     /* If there were multiple types specified in the decl-specifier-seq,
>        issue an error message.  */
>     if (declspecs->multiple_types_p)
> @@ -11155,6 +11194,12 @@ grokdeclarator (const cp_declarator *declarator,
>   		    "a parameter cannot be declared %<constexpr%>");
>             constexpr_p = 0;
>           }
> +      else if (constinit_p)
> +	{
> +	  error_at (declspecs->locations[ds_constinit],
> +		    "a parameter cannot be declared %<constinit%>");
> +	  constexpr_p = 0;
> +	}
>       }
>   
>     /* Give error if `virtual' is used outside of class declaration.  */
> @@ -11595,6 +11640,13 @@ grokdeclarator (const cp_declarator *declarator,
>   			  "an array", name);
>   		return error_mark_node;
>   	      }
> +	    if (constinit_p)
> +	      {
> +		error_at (declspecs->locations[ds_constinit],
> +			  "%<constinit%> on function return type is not "
> +			  "allowed");
> +		return error_mark_node;
> +	      }
>   
>   	    if (ctype == NULL_TREE
>   		&& decl_context == FIELD
> @@ -12792,10 +12844,17 @@ grokdeclarator (const cp_declarator *declarator,
>                   else if (constexpr_p)
>   		  {
>   		    error_at (declspecs->locations[ds_constexpr],
> -			      "non-static data member %qE declared %<constexpr%>",
> -			      unqualified_id);
> +			      "non-static data member %qE declared "
> +			      "%<constexpr%>", unqualified_id);
>   		    constexpr_p = false;
>   		  }
> +		else if (constinit_p)
> +		  {
> +		    error_at (declspecs->locations[ds_constinit],
> +			      "non-static data member %qE declared "
> +			      "%<constinit%>", unqualified_id);
> +		    constinit_p = false;
> +		  }
>   		decl = build_decl (id_loc, FIELD_DECL, unqualified_id, type);
>   		DECL_NONADDRESSABLE_P (decl) = bitfield;
>   		if (bitfield && !unqualified_id)
> @@ -13069,6 +13128,9 @@ grokdeclarator (const cp_declarator *declarator,
>       /* Set constexpr flag on vars (functions got it in grokfndecl).  */
>       if (constexpr_p && VAR_P (decl))
>         DECL_DECLARED_CONSTEXPR_P (decl) = true;
> +    /* And the constinit flag (which only applies to variables).  */
> +    else if (constinit_p && VAR_P (decl))
> +      DECL_DECLARED_CONSTINIT_P (decl) = true;
>   
>       /* Record constancy and volatility on the DECL itself .  There's
>          no need to do this when processing a template; we'll do this
> diff --git gcc/cp/lex.c gcc/cp/lex.c
> index 12567da39c4..5b43723a8fa 100644
> --- gcc/cp/lex.c
> +++ gcc/cp/lex.c
> @@ -229,6 +229,8 @@ init_reswords (void)
>   
>     if (cxx_dialect < cxx11)
>       mask |= D_CXX11;
> +  if (cxx_dialect < cxx2a)
> +    mask |= D_CXX20;
>     if (!flag_concepts)
>       mask |= D_CXX_CONCEPTS;
>     if (!flag_tm)
> diff --git gcc/cp/parser.c gcc/cp/parser.c
> index b56cc6924f4..d5587e2b1fc 100644
> --- gcc/cp/parser.c
> +++ gcc/cp/parser.c
> @@ -834,14 +834,28 @@ cp_lexer_get_preprocessor_token (cp_lexer *lexer, cp_token *token)
>               {
>                 /* Warn about the C++0x keyword (but still treat it as
>                    an identifier).  */
> -              warning (OPT_Wc__11_compat,
> -                       "identifier %qE is a keyword in C++11",
> -                       token->u.value);
> +	      warning_at (token->location, OPT_Wc__11_compat,
> +			  "identifier %qE is a keyword in C++11",
> +			  token->u.value);
>   
>                 /* Clear out the C_RID_CODE so we don't warn about this
>                    particular identifier-turned-keyword again.  */
>                 C_SET_RID_CODE (token->u.value, RID_MAX);
>               }
> +	  if (warn_cxx20_compat
> +	      && C_RID_CODE (token->u.value) >= RID_FIRST_CXX20
> +	      && C_RID_CODE (token->u.value) <= RID_LAST_CXX20)
> +	    {
> +	      /* Warn about the C++20 keyword (but still treat it as
> +		 an identifier).  */
> +	      warning_at (token->location, OPT_Wc__20_compat,
> +			  "identifier %qE is a keyword in C++20",
> +			  token->u.value);
> +
> +	      /* Clear out the C_RID_CODE so we don't warn about this
> +		 particular identifier-turned-keyword again.  */
> +	      C_SET_RID_CODE (token->u.value, RID_MAX);
> +	    }
>   
>   	  token->keyword = RID_MAX;
>   	}
> @@ -986,6 +1000,7 @@ cp_keyword_starts_decl_specifier_p (enum rid keyword)
>       case RID_DECLTYPE:
>       case RID_UNDERLYING_TYPE:
>       case RID_CONSTEXPR:
> +    case RID_CONSTINIT:
>         return true;
>   
>       default:
> @@ -3355,6 +3370,9 @@ cp_parser_diagnose_invalid_type_name (cp_parser *parser, tree id,
>   	       && id_equal (id, "thread_local"))
>   	inform (location, "C++11 %<thread_local%> only available with "
>   		"%<-std=c++11%> or %<-std=gnu++11%>");
> +      else if (cxx_dialect < cxx2a && id == ridpointers[(int)RID_CONSTINIT])
> +	inform (location, "C++20 %<constinit%> only available with "
> +		"%<-std=c++2a%> or %<-std=gnu++2a%>");
>         else if (!flag_concepts && id == ridpointers[(int)RID_CONCEPT])
>   	inform (location, "%<concept%> only available with %<-fconcepts%>");
>         else if (processing_template_decl && current_class_type
> @@ -14001,7 +14019,8 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
>   	{
>   	  /* decl-specifier:
>   	       friend
> -               constexpr */
> +	       constexpr
> +	       constinit */
>   	case RID_FRIEND:
>   	  if (!at_class_scope_p ())
>   	    {
> @@ -14023,6 +14042,11 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
>             cp_lexer_consume_token (parser->lexer);
>             break;
>   
> +	case RID_CONSTINIT:
> +	  ds = ds_constinit;
> +	  cp_lexer_consume_token (parser->lexer);
> +	  break;
> +
>           case RID_CONCEPT:
>             ds = ds_concept;
>             cp_lexer_consume_token (parser->lexer);
> @@ -29510,7 +29534,8 @@ set_and_check_decl_spec_loc (cp_decl_specifier_seq *decl_specs,
>   	    "typedef",
>   	    "using",
>               "constexpr",
> -	    "__complex"
> +	    "__complex",
> +	    "constinit"
>   	  };
>   	  gcc_rich_location richloc (location);
>   	  richloc.add_fixit_remove ();
> diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c
> index 02c3ad5efb0..da51b4232d9 100644
> --- gcc/cp/typeck2.c
> +++ gcc/cp/typeck2.c
> @@ -885,7 +885,22 @@ store_init_value (tree decl, tree init, vec<tree, va_gc>** cleanups, int flags)
>         if (!TYPE_REF_P (type))
>   	TREE_CONSTANT (decl) = const_init && decl_maybe_constant_var_p (decl);
>         if (!const_init)
> -	value = oldval;
> +	{
> +	  /* [dcl.constinit]/2 "If a variable declared with the constinit
> +	     specifier has dynamic initialization, the program is
> +	     ill-formed."  */
> +	  if (DECL_DECLARED_CONSTINIT_P (decl))
> +	    {
> +	      error_at (location_of (decl),
> +			"%<constinit%> variable %qD does not have a constant "
> +			"initializer", decl);
> +	      if (require_constant_expression (value))
> +		cxx_constant_init (value, decl);
> +	      value = error_mark_node;
> +	    }
> +	  else
> +	    value = oldval;
> +	}
>       }
>     value = cp_fully_fold_init (value);
>   
> diff --git gcc/doc/invoke.texi gcc/doc/invoke.texi
> index ca111792885..8782317db96 100644
> --- gcc/doc/invoke.texi
> +++ gcc/doc/invoke.texi
> @@ -295,6 +295,7 @@ Objective-C and Objective-C++ Dialects}.
>   -Wno-builtin-declaration-mismatch @gol
>   -Wno-builtin-macro-redefined  -Wc90-c99-compat  -Wc99-c11-compat @gol
>   -Wc++-compat  -Wc++11-compat  -Wc++14-compat  -Wc++17-compat  @gol
> +-Wc++20-compat  @gol
>   -Wcast-align  -Wcast-align=strict  -Wcast-function-type  -Wcast-qual  @gol
>   -Wchar-subscripts  -Wcatch-value  -Wcatch-value=@var{n} @gol
>   -Wclobbered  -Wcomment  -Wconditionally-supported @gol
> @@ -6792,6 +6793,12 @@ and ISO C++ 2014.  This warning is enabled by @option{-Wall}.
>   Warn about C++ constructs whose meaning differs between ISO C++ 2014
>   and ISO C++ 2017.  This warning is enabled by @option{-Wall}.
>   
> +@item -Wc++20-compat @r{(C++ and Objective-C++ only)}
> +@opindex Wc++20-compat
> +@opindex Wno-c++20-compat
> +Warn about C++ constructs whose meaning differs between ISO C++ 2017
> +and ISO C++ 2020.  This warning is enabled by @option{-Wall}.
> +
>   @item -Wcast-qual
>   @opindex Wcast-qual
>   @opindex Wno-cast-qual
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit1.C gcc/testsuite/g++.dg/cpp2a/constinit1.C
> new file mode 100644
> index 00000000000..9d1c0289a62
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit1.C
> @@ -0,0 +1,38 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++2a } }
> +// Test basic usage of 'constinit'.
> +
> +const char *g() { return "dynamic init"; }
> +constexpr const char *f(bool p) { return p ? "constant init" : g(); } // { dg-error "call to non-.constexpr. function" }
> +
> +constinit const char *c = f(true);
> +constinit const char *d = f(false); // { dg-error "variable .d. does not have a constant initializer" }
> +// { dg-message "in .constexpr. expansion of" "" { target *-*-* } .-1 }
> +static constinit const char *e = f(true);
> +
> +constexpr int foo(int x) { return x; }
> +constinit int i = foo(42);
> +constinit int j // { dg-error "variable .j. does not have a constant initializer" }
> +  = foo(i); // { dg-error "not usable in a constant expression" }
> +
> +int y = 42;
> +constinit int x // { dg-error "variable .x. does not have a constant initializer" }
> +  = y; // { dg-error "not usable in a constant expression" }
> +
> +constinit int z;
> +const constinit unsigned cst = 1u;
> +
> +void
> +fn ()
> +{
> +  static constinit int m = foo(42);
> +  static constinit int n // { dg-error "variable .n. does not have a constant initializer" }
> +    = foo(m); // { dg-error "not usable in a constant expression" }
> +
> +  // Make sure we can still modify constinit variables.
> +  c = "foo";
> +  i = 10;
> +  m = 90;
> +  // ... unless they're 'const'.
> +  cst *= 2; // { dg-error "assignment of read-only variable" }
> +}
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit10.C gcc/testsuite/g++.dg/cpp2a/constinit10.C
> new file mode 100644
> index 00000000000..a50f285ecb1
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit10.C
> @@ -0,0 +1,26 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++2a } }
> +// From PR83428.
> +
> +struct S1
> +{
> +    constexpr S1 ();
> +    int m_i;
> +};
> +
> +struct alignas(64) S2
> +{
> +    constexpr S2 ()
> +    : m_tabS1()
> +    {}
> +
> +    S1 m_tabS1[7];
> +};
> +
> +constinit S2 objX; // { dg-error ".constinit. variable .objX. does not have a constant initializer" }
> +// { dg-error "used before its definition" "" { target *-*-* } .-1 }
> +// // { dg-message "in .constexpr. expansion of" "" { target *-*-* } .-2 }
> +
> +constexpr S1::S1 ()
> +: m_i(14)
> +{}
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit11.C gcc/testsuite/g++.dg/cpp2a/constinit11.C
> new file mode 100644
> index 00000000000..acdda73d1cf
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit11.C
> @@ -0,0 +1,79 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++2a } }
> +
> +int foo ();
> +constexpr int constfoo () { return 42; }
> +int gl = 42;
> +
> +struct nonliteral {
> +  int m;
> +  nonliteral() : m() { }
> +  nonliteral(int n) : m(n) { }
> +  ~nonliteral() {}
> +};
> +
> +struct literal {
> +  int m;
> +  constexpr literal() : m() { }
> +  constexpr literal(int n) : m(n) { }
> +};
> +
> +struct pod {
> +  int m;
> +};
> +
> +struct S {
> +  static constinit pod p;
> +  static constinit pod pc;
> +  static const constinit nonliteral n;
> +};
> +
> +struct W {
> +  int w = 42;
> +};
> +
> +constinit W w;
> +
> +constinit const int &r1 = gl;
> +constinit thread_local const int &r2 = gl;
> +constinit const int &r3 // { dg-error "variable .r3. does not have a constant initializer" }
> +  = foo (); // { dg-error "call to non-.constexpr. function" }
> +constinit const literal &r4 = 42;
> +constinit const nonliteral &r5 // { dg-error "variable .r5. does not have a constant initializer" }
> +  = 42; // { dg-error "call to non-.constexpr. function" }
> +constinit const int &r6 = nonliteral(2).m; // { dg-error "variable .r6. does not have a constant initializer|call to non-.constexpr. function" }
> +
> +constinit pod p1;
> +constinit pod p2 = { 42 };
> +constinit pod p3 = { constfoo() };
> +constinit pod p4 = { foo() }; // { dg-error "variable .p4. does not have a constant initializer|call to non-.constexpr. function" }
> +
> +constexpr literal lit;
> +constinit literal l1 = lit;
> +constinit literal l2 = 42;
> +constinit literal l3 = constfoo();
> +constinit literal l4 = foo(); // { dg-error "variable .l4. does not have a constant initializer|call to non-.constexpr. function" }
> +constinit literal l5 = {};
> +constinit literal l6{};
> +constinit thread_local literal l7 = lit;
> +constinit thread_local literal l8 = 42;
> +constinit thread_local literal l9 = constfoo();
> +constinit thread_local literal l10 = foo(); // { dg-error "variable .l10. does not have a constant initializer|call to non-.constexpr. function" }
> +constinit thread_local literal l11{};
> +
> +pod S::p;
> +pod S::pc(S::p); // { dg-error "variable .S::pc. does not have a constant initializer|not usable" }
> +
> +const nonliteral S::n(42); // { dg-error "variable .S::n. does not have a constant initializer|call to non-.constexpr. function" }
> +constinit int n1 = nonliteral{42}.m; // { dg-error "variable .n1. does not have a constant initializer|temporary of non-literal type" }
> +constinit int n2 = literal{42}.m;
> +
> +void
> +fn1 ()
> +{
> +  const int c = 42;
> +  static constinit const int &l // { dg-error "variable .l. does not have a constant initializer" }
> +    = c; // { dg-error "not a constant" }
> +  static const int &l2 = 10;
> +  static const int &l3 = gl;
> +}
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit12.C gcc/testsuite/g++.dg/cpp2a/constinit12.C
> new file mode 100644
> index 00000000000..b5b736f87c2
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit12.C
> @@ -0,0 +1,14 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++2a } }
> +
> +struct S {
> +  S(int) { }
> +};
> +
> +template <class T>
> +struct U {
> +  T m;
> +  constexpr U(int i) : m(i) { } // { dg-error "call to non-.constexpr. function" }
> +};
> +
> +constinit U<S> u(42); // { dg-error "does not have a constant initializer|called in a constant expression" }
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit2.C gcc/testsuite/g++.dg/cpp2a/constinit2.C
> new file mode 100644
> index 00000000000..3e9f578f8ca
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit2.C
> @@ -0,0 +1,14 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++11 } }
> +// Test that 'constinit' isn't recognized pre-C++2a, but '__constinit' is.
> +
> +constinit int g = 42; // { dg-error ".constinit. does not name a type" "" { target c++17_down } }
> +__constinit int g2 = 42;
> +static __constinit int g3 = 42;
> +
> +void
> +fn ()
> +{
> +  static constinit int x = 69; // { dg-error ".constinit. does not name a type" "" { target c++17_down } }
> +  static __constinit int x2 = 69;
> +}
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit3.C gcc/testsuite/g++.dg/cpp2a/constinit3.C
> new file mode 100644
> index 00000000000..316937e5bf3
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit3.C
> @@ -0,0 +1,58 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++2a } }
> +
> +constinit constinit int v1; // { dg-error "duplicate .constinit." }
> +constexpr constinit int v2 = 1; // { dg-error "can use at most one of the .constinit. and .constexpr. specifiers" }
> +constinit constexpr int v3 = 1; // { dg-error "an use at most one of the .constinit. and .constexpr. specifiers" }
> +
> +extern static constinit int v4; // { dg-error "conflicting specifiers" }
> +extern thread_local constinit int v5;
> +extern constinit int v6;
> +
> +constinit typedef int T; // { dg-error ".constinit. cannot appear in a typedef declaration" }
> +
> +struct S2 {
> +  constinit int m1; // { dg-error "non-static data member .m1. declared .constinit." }
> +  constinit unsigned int b : 32; // { dg-error " non-static data member .b. declared .constinit." }
> +};
> +
> +struct S3 {
> +  constinit S3() {} // { dg-error ".constinit. on function return type is not allowed" }
> +  constinit ~S3() {} // { dg-error ".constinit. on function return type is not allowed" }
> +};
> +
> +constinit struct S4 { // { dg-error ".constinit. cannot be used for type declarations" }
> +};
> +
> +template<constinit int I> // { dg-error "a parameter cannot be declared .constinit." }
> +struct X { };
> +
> +int
> +fn1 ()
> +{
> +  // Not static storage
> +  constinit int a1 = 42; // { dg-error ".constinit. can only be applied to a variable with static or thread storage" }
> +  constinit int a2 = 42; // { dg-error ".constinit. can only be applied to a variable with static or thread storage" }
> +  extern constinit int e1;
> +
> +  return 0;
> +}
> +
> +constinit int // { dg-error ".constinit. on function return type is not allowed" }
> +fn3 ()
> +{
> +}
> +
> +void
> +fn2 (int i, constinit int p) // { dg-error "a parameter cannot be declared .constinit." }
> +{
> +  constinit auto l = [i](){ return i; }; // { dg-error ".constinit. can only be applied to a variable with static or thread storage" }
> +}
> +
> +struct B { int d; };
> +
> +void
> +fn3 (B b)
> +{
> +  constinit auto [ a ] = b; // { dg-error ".constinit. can only be applied to a variable with static or thread storage" }
> +}
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit4.C gcc/testsuite/g++.dg/cpp2a/constinit4.C
> new file mode 100644
> index 00000000000..748a7ffa3a9
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit4.C
> @@ -0,0 +1,16 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++2a } }
> +
> +struct S { };
> +constinit extern S s;
> +constinit S s2 = { };
> +
> +struct T {
> +  int i;
> +};
> +
> +constinit T t;
> +struct U : T {
> +  int j;
> +};
> +constinit U u;
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit5.C gcc/testsuite/g++.dg/cpp2a/constinit5.C
> new file mode 100644
> index 00000000000..9832a561bf8
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit5.C
> @@ -0,0 +1,27 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++2a } }
> +// Check that we preserve DECL_DECLARED_CONSTINIT_P in duplicate_decls.
> +
> +int gl = 42;
> +
> +struct S {
> +  constinit static int m;
> +  constinit static int n;
> +  constinit static const int &r1;
> +  constinit static const int &r2;
> +};
> +
> +int S::m = 42;
> +int nonconst;
> +int S::n = nonconst; // { dg-error "variable .S::n. does not have a constant initializer" }
> +// { dg-error "not usable in a constant expression" "" { target *-*-* } .-1 }
> +
> +const int &S::r1 = gl;
> +const int &S::r2 = 42;
> +
> +struct T {
> +  constinit static thread_local const int &r1;
> +  constinit static thread_local const int &r2;
> +};
> +thread_local const int &T::r1 = gl;
> +thread_local const int &T::r2 = 42; // { dg-error "variable .T::r2. does not have a constant initializer|not a constant expression" }
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit6.C gcc/testsuite/g++.dg/cpp2a/constinit6.C
> new file mode 100644
> index 00000000000..73e78832844
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit6.C
> @@ -0,0 +1,5 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++17_down } }
> +// { dg-options "-Wc++20-compat" }
> +
> +int constinit; // { dg-warning "identifier .constinit. is a keyword" }
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit7.C gcc/testsuite/g++.dg/cpp2a/constinit7.C
> new file mode 100644
> index 00000000000..e9a0da3f8c8
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit7.C
> @@ -0,0 +1,11 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++11 } }
> +
> +struct T {
> +  constexpr T(int) {}
> +  ~T();
> +};
> +__constinit T x = { 42 };
> +// ??? This should be rejected in C++14: copy initialization is not a constant
> +// expression on a non-literal type in C++14.  But 'constinit' is C++20 only.
> +__constinit T y = 42;
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit8.C gcc/testsuite/g++.dg/cpp2a/constinit8.C
> new file mode 100644
> index 00000000000..c6b2975350c
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit8.C
> @@ -0,0 +1,18 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do compile { target c++2a } }
> +// Variable templates.
> +
> +int nonconst;
> +
> +template<typename T>
> +constinit T v1 = 42;
> +
> +template<typename T>
> +constinit T v2 = nonconst; // { dg-error "does not have a constant initializer|not usable" }
> +
> +void
> +fn ()
> +{
> +  v1<int>;
> +  v2<int>;
> +}
> diff --git gcc/testsuite/g++.dg/cpp2a/constinit9.C gcc/testsuite/g++.dg/cpp2a/constinit9.C
> new file mode 100644
> index 00000000000..4c7f8925169
> --- /dev/null
> +++ gcc/testsuite/g++.dg/cpp2a/constinit9.C
> @@ -0,0 +1,24 @@
> +// PR c++/91360 - Implement C++20 P1143R2: constinit
> +// { dg-do run { target c++2a } }
> +// A run-time test.
> +
> +constexpr int foo (int x) { return x; }
> +constinit int b = foo(42);
> +
> +int
> +main ()
> +{
> +  if (b != 42)
> +    __builtin_abort ();
> +  // We can still modify 'b'.
> +  b = 10;
> +  if (b != 10)
> +    __builtin_abort ();
> +
> +  constinit static int s = foo(14);
> +  if (s != 14)
> +    __builtin_abort ();
> +  s++;
> +  if (s != 15)
> +    __builtin_abort ();
> +}
> 



More information about the Gcc-patches mailing list