[2/3] c++: P1997 array-copy extensions: Initialization [PR103238]

Message ID 20211122025114.3167997-3-wjwray@gmail.com
State New
Headers
Series P1997 'array-copy' patchset [PR103238] |

Commit Message

will wray Nov. 22, 2021, 2:51 a.m. UTC
  This patch implements initializations of arrays from array values.

The first of two 'array-copy' patches, it adds the option -farray-copy
(flag_array_copy) to enable all features of P1997 (copy related or not),
documented as experimental extensions.

It deals with initialization of array variables and member array fields.

Initialization of an array variable from an array of the same type performs
array copy-initialization; elementwise move or copy from an rvalue or lvalue
array respectively, in index order from begin to end. The existing code path
for a structured binding declaration with array initializer, auto[e...]{a};
performs the same array copy-initialization (as a special case superpower).
Borrowing from that, this was a relatively quick and easy change.

Initialization of member arrays proved much more difficult to do in general.
I resorted to trial and error, running gcc in gdb with test cases to work out
where and what to change, until eventually converging on this set of changes.

One starting point was the C special case of char array initialization from
string literals (as char array lvalue constants). However, a long-standing
bug in designated initialization of char arrays by string literals blocked
the task of extending this special case to general array type initializers.
A bugfix patch was separated out, to be merged ahead of these patches:

    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55227
    https://gcc.gnu.org/pipermail/gcc-patches/2021-November/584897.html

Other cases to consider, array initializations:

    * by optionally brace-enclosed or paren-enclosed array values
    * by possibly-designated array-valued aggregate initializers
      (within possibly-elided braced init-lists)
    * by brace or paren-enclosed array values in member initialization lists
    * by array-valued member initializers

The patch adds tests for these cases, and for inner initializations of nested
array elements of array type.

The work has diverged in details from the P1997 wording, including catching
up with C++20 changes such as parenthesised initialization of aggregates.
The paper will be revised to reflect the implementation experience.

It is likely that there are omissions, errors in the conditions or that changed
code is inappropriate. For example, I inserted a new call to build_array_copy
in typeck2.c:digest_init_r which may not be correct for move-enabled elements.
Please review carefully with this in mind and suggest test cases to exercise.

	PR c++/103238

gcc/c-family/ChangeLog:

	* c-common.c (complete_array_type): Accept array type initial_value.
	* c.opt: New option -farray-copy "experimental extensions for P1997".

gcc/cp/ChangeLog:

	* decl.c (do_aggregate_paren_init): Accept single array type init.
	(maybe_deduce_size_from_array_init): Include same-type array inits,
	or complain for not same-type arrays.
	(reshape_init_r): Extend string-literal handling to all array types.
	* init.c (build_aggr_init): Follow existing path for array rhs.
	* typeck.c (cp_build_modify_expr): Follow path for synthetic op=.
	* typeck2.c (digest_init_r): Add call to build_array_copy for
	same-type arrays ('copy' feels wrong for move-eligible rhs).

gcc/ChangeLog:

	* doc/invoke.texi: -farray-copy help info documentation.

gcc/testsuite/ChangeLog:

	* g++.dg/init/array-copy1.C: New test. Variable init 'before' XFAILs
	* g++.dg/init/array-copy2.C: New test. Variable init 'after' PASSes
	* g++.dg/init/array-copy3.C: New test. Member init 'before' XFAILs
	* g++.dg/init/array-copy4.C: New test. Member init 'after' PASSes
	* g++.dg/init/array-copy5.C: New test. Member nsdmi & desig XFAILs
	* g++.dg/init/array-copy6.C: New test. Member nsdmi & desig PASSes
---
 gcc/c-family/c-common.c                 |  5 +++
 gcc/c-family/c.opt                      |  4 ++
 gcc/cp/decl.c                           | 61 ++++++++++++++++++++---------
 gcc/cp/init.c                           |  6 ++-
 gcc/cp/typeck.c                         |  9 +++--
 gcc/cp/typeck2.c                        | 30 +++++++++++----
 gcc/doc/invoke.texi                     |  6 +++
 gcc/testsuite/g++.dg/init/array-copy1.C | 66 ++++++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/init/array-copy2.C | 68 +++++++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/init/array-copy3.C | 41 ++++++++++++++++++++
 gcc/testsuite/g++.dg/init/array-copy4.C | 42 ++++++++++++++++++++
 gcc/testsuite/g++.dg/init/array-copy5.C | 36 +++++++++++++++++
 gcc/testsuite/g++.dg/init/array-copy6.C | 51 +++++++++++++++++++++++++
 13 files changed, 395 insertions(+), 30 deletions(-)
  

Comments

Jason Merrill Nov. 27, 2021, 10:56 p.m. UTC | #1
On 11/21/21 21:51, Will Wray via Gcc-patches wrote:
> This patch implements initializations of arrays from array values.
> 
> The first of two 'array-copy' patches, it adds the option -farray-copy
> (flag_array_copy) to enable all features of P1997 (copy related or not),
> documented as experimental extensions.
> 
> It deals with initialization of array variables and member array fields.
> 
> Initialization of an array variable from an array of the same type performs
> array copy-initialization; elementwise move or copy from an rvalue or lvalue
> array respectively, in index order from begin to end. The existing code path
> for a structured binding declaration with array initializer, auto[e...]{a};
> performs the same array copy-initialization (as a special case superpower).
> Borrowing from that, this was a relatively quick and easy change.
> 
> Initialization of member arrays proved much more difficult to do in general.
> I resorted to trial and error, running gcc in gdb with test cases to work out
> where and what to change, until eventually converging on this set of changes.
> 
> One starting point was the C special case of char array initialization from
> string literals (as char array lvalue constants). However, a long-standing
> bug in designated initialization of char arrays by string literals blocked
> the task of extending this special case to general array type initializers.
> A bugfix patch was separated out, to be merged ahead of these patches:
> 
>      https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55227
>      https://gcc.gnu.org/pipermail/gcc-patches/2021-November/584897.html
> 
> Other cases to consider, array initializations:
> 
>      * by optionally brace-enclosed or paren-enclosed array values
>      * by possibly-designated array-valued aggregate initializers
>        (within possibly-elided braced init-lists)
>      * by brace or paren-enclosed array values in member initialization lists
>      * by array-valued member initializers
> 
> The patch adds tests for these cases, and for inner initializations of nested
> array elements of array type.
> 
> The work has diverged in details from the P1997 wording, including catching
> up with C++20 changes such as parenthesised initialization of aggregates.
> The paper will be revised to reflect the implementation experience.
> 
> It is likely that there are omissions, errors in the conditions or that changed
> code is inappropriate. For example, I inserted a new call to build_array_copy
> in typeck2.c:digest_init_r which may not be correct for move-enabled elements.
> Please review carefully with this in mind and suggest test cases to exercise.

You would want a testcase with an array of move-only type, e.g. (untested)

struct A { A(int); A(A&&); int i; };
using Ar = A[2];
struct B { A a[2]; };
B b = { Ar{1,2} }; // should move

I don't think the new call to build_array_copy needs to change, but it's 
good to test.

> 	PR c++/103238
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-common.c (complete_array_type): Accept array type initial_value.
> 	* c.opt: New option -farray-copy "experimental extensions for P1997".
> 
> gcc/cp/ChangeLog:
> 
> 	* decl.c (do_aggregate_paren_init): Accept single array type init.
> 	(maybe_deduce_size_from_array_init): Include same-type array inits,
> 	or complain for not same-type arrays.
> 	(reshape_init_r): Extend string-literal handling to all array types.
> 	* init.c (build_aggr_init): Follow existing path for array rhs.
> 	* typeck.c (cp_build_modify_expr): Follow path for synthetic op=.
> 	* typeck2.c (digest_init_r): Add call to build_array_copy for
> 	same-type arrays ('copy' feels wrong for move-eligible rhs).
> 
> gcc/ChangeLog:
> 
> 	* doc/invoke.texi: -farray-copy help info documentation.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/init/array-copy1.C: New test. Variable init 'before' XFAILs
> 	* g++.dg/init/array-copy2.C: New test. Variable init 'after' PASSes
> 	* g++.dg/init/array-copy3.C: New test. Member init 'before' XFAILs
> 	* g++.dg/init/array-copy4.C: New test. Member init 'after' PASSes
> 	* g++.dg/init/array-copy5.C: New test. Member nsdmi & desig XFAILs
> 	* g++.dg/init/array-copy6.C: New test. Member nsdmi & desig PASSes
> ---
>   gcc/c-family/c-common.c                 |  5 +++
>   gcc/c-family/c.opt                      |  4 ++
>   gcc/cp/decl.c                           | 61 ++++++++++++++++++++---------
>   gcc/cp/init.c                           |  6 ++-
>   gcc/cp/typeck.c                         |  9 +++--
>   gcc/cp/typeck2.c                        | 30 +++++++++++----
>   gcc/doc/invoke.texi                     |  6 +++
>   gcc/testsuite/g++.dg/init/array-copy1.C | 66 ++++++++++++++++++++++++++++++++
>   gcc/testsuite/g++.dg/init/array-copy2.C | 68 +++++++++++++++++++++++++++++++++
>   gcc/testsuite/g++.dg/init/array-copy3.C | 41 ++++++++++++++++++++
>   gcc/testsuite/g++.dg/init/array-copy4.C | 42 ++++++++++++++++++++
>   gcc/testsuite/g++.dg/init/array-copy5.C | 36 +++++++++++++++++
>   gcc/testsuite/g++.dg/init/array-copy6.C | 51 +++++++++++++++++++++++++
>   13 files changed, 395 insertions(+), 30 deletions(-)
> 
> diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
> index 86c007f53de..fb0b1ef294f 100644
> --- a/gcc/c-family/c-common.c
> +++ b/gcc/c-family/c-common.c
> @@ -6796,6 +6796,11 @@ complete_array_type (tree *ptype, tree initial_value, bool do_default)
>   	    = int_size_in_bytes (TREE_TYPE (TREE_TYPE (initial_value)));
>   	  maxindex = size_int (TREE_STRING_LENGTH (initial_value)/eltsize - 1);
>   	}
> +      else if (flag_array_copy
> +	       && TREE_CODE (TREE_TYPE (initial_value)) == ARRAY_TYPE)
> +	{
> +	  maxindex = array_type_nelts (TREE_TYPE (initial_value));
> +	}
>         else if (TREE_CODE (initial_value) == CONSTRUCTOR)
>   	{
>   	  vec<constructor_elt, va_gc> *v = CONSTRUCTOR_ELTS (initial_value);
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index 3976fc368db..9d2a6ad8e1c 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -1579,6 +1579,10 @@ fcilkplus
>   C ObjC C++ ObjC++ LTO Undocumented Ignore
>   Removed in GCC 8.  This switch has no effect.
>   
> +farray-copy
> +C ObjC C++ ObjC++ Var(flag_array_copy)
> +Enable experimental extension for P1997; C array copy semantics.
> +
>   fconcepts
>   C++ ObjC++ Var(flag_concepts)
>   Enable support for C++ concepts.
> diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
> index 83a2d3bf8f1..1010fa2c53f 100644
> --- a/gcc/cp/decl.c
> +++ b/gcc/cp/decl.c
> @@ -5834,15 +5834,19 @@ tree
>   do_aggregate_paren_init (tree init, tree type)
>   {
>     tree val = TREE_VALUE (init);
> +  tree stripped_val = tree_strip_any_location_wrapper (val);
>   
>     if (TREE_CHAIN (init) == NULL_TREE)
>       {
>         /* If the list has a single element and it's a string literal,
>   	 then it's the initializer for the array as a whole.  */
> -      if (TREE_CODE (type) == ARRAY_TYPE
> -	  && char_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (type)))
> -	  && TREE_CODE (tree_strip_any_location_wrapper (val))
> -	     == STRING_CST)
> +      if ((TREE_CODE (type) == ARRAY_TYPE
> +	   && char_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (type)))
> +	   && TREE_CODE (stripped_val) == STRING_CST)
> +	  || (flag_array_copy
> +	      && TREE_CODE (TREE_TYPE (stripped_val)) == ARRAY_TYPE
> +	      && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (type),
> +					TREE_TYPE (TREE_TYPE (stripped_val)))))
>   	return val;
>         /* Handle non-standard extensions like compound literals.  This also
>   	 prevents triggering aggregate parenthesized-initialization in
> @@ -6007,9 +6011,10 @@ maybe_deduce_size_from_array_init (tree decl, tree init)
>   {
>     tree type = TREE_TYPE (decl);
>   
> -  if (TREE_CODE (type) == ARRAY_TYPE
> -      && TYPE_DOMAIN (type) == NULL_TREE
> -      && TREE_CODE (decl) != TYPE_DECL)
> +  if (TREE_CODE (type) != ARRAY_TYPE
> +      || TREE_CODE (decl) == TYPE_DECL)
> +    ;
> +  else if (TYPE_DOMAIN (type) == NULL_TREE)
>       {
>         /* do_default is really a C-ism to deal with tentative definitions.
>   	 But let's leave it here to ease the eventual merge.  */
> @@ -6072,6 +6077,17 @@ maybe_deduce_size_from_array_init (tree decl, tree init)
>   
>         relayout_decl (decl);
>       }
> +  else if (flag_array_copy && init
> +	   && TREE_CODE (TREE_TYPE (init)) == ARRAY_TYPE
> +	   && TREE_CODE (tree_strip_any_location_wrapper (init)) != STRING_CST
> +	   && !same_type_ignoring_top_level_qualifiers_p (type,
> +							  TREE_TYPE (init)))
> +    {
> +      error_at (DECL_SOURCE_LOCATION (decl),
> +		"array of type %qT cannot be initialized with an "
> +		"array of type %qT in %qD", type, TREE_TYPE (init), decl);
> +      TREE_TYPE (decl) = error_mark_node;
> +    }
>   }
>   
>   /* Set DECL_SIZE, DECL_ALIGN, etc. for DECL (a VAR_DECL), and issue
> @@ -6815,21 +6831,23 @@ reshape_init_r (tree type, reshape_iter *d, tree first_initializer_p,
>         return init;
>       }
>   
> -  /* [dcl.init.string]
> +  /* [dcl.init.string] or flag_array_copy extension P1997
>   
>         A char array (whether plain char, signed char, or unsigned char)
>         can be initialized by a string-literal (optionally enclosed in
>         braces); a wchar_t array can be initialized by a wide
> -      string-literal (optionally enclosed in braces).  */
> -  if (TREE_CODE (type) == ARRAY_TYPE
> -      && char_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (type))))
> +      string-literal (optionally enclosed in braces).
> +      With flag_array_copy an array can be initialized by an array value
> +      (optionally enclosed in braces).  */
> +  if (TREE_CODE (type) == ARRAY_TYPE)
>       {
>         tree arr_init = init;
>         tree stripped_arr_init = stripped_init;

The renaming belongs in this patch.

>         reshape_iter stripd = {};
>   
> -      /* Strip one level of braces if and only if they enclose a single
> -	 element (as allowed by [dcl.init.string]).  */
> +      /* Strip one level of braces if they enclose a single element,
> +	 provisionally for elison in the optionally-braced case if the
> +	 initializer is of compatible array type, as checked next. */
>         if (!first_initializer_p
>   	  && TREE_CODE (stripped_arr_init) == CONSTRUCTOR
>   	  && CONSTRUCTOR_NELTS (stripped_arr_init) == 1)
> @@ -6839,10 +6857,19 @@ reshape_init_r (tree type, reshape_iter *d, tree first_initializer_p,
>   	  stripped_arr_init = tree_strip_any_location_wrapper (arr_init);
>   	}
>   
> -      /* If it's a string literal, then it's the initializer for the array
> -	 as a whole. Otherwise, continue with normal initialization for
> -	 array types (one value per array element).  */
> -      if (TREE_CODE (stripped_arr_init) == STRING_CST)
> +      tree init_type = TREE_TYPE (stripped_arr_init);
> +
> +      /* If it's a string literal initializer for a char array target,
> +	 or, with flag_array_copy, an array value of same element type,
> +	 then it's the initializer for the array as a whole.
> +	 Else, continue with element by element array initialization.  */
> +      if ((TREE_CODE (stripped_arr_init) == STRING_CST
> +	   && char_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (type))))
> +	  || (flag_array_copy
> +	      && TREE_CODE (init_type) == ARRAY_TYPE
> +	      && TYPE_DOMAIN (init_type) != NULL_TREE
> +	      && same_type_ignoring_top_level_qualifiers_p (
> +				TREE_TYPE (type), TREE_TYPE (init_type))))
>   	{
>   	  if ((first_initializer_p && has_designator_problem (d, complain))
>   	      || (stripd.cur && has_designator_problem (&stripd, complain)))
> diff --git a/gcc/cp/init.c b/gcc/cp/init.c
> index 3ba2e3bbe04..fd9a065ed95 100644
> --- a/gcc/cp/init.c
> +++ b/gcc/cp/init.c
> @@ -1787,7 +1787,11 @@ build_aggr_init (tree exp, tree init, int flags, tsubst_flags_t complain)
>         tree itype = init ? TREE_TYPE (init) : NULL_TREE;
>         int from_array = 0;
>   
> -      if (VAR_P (exp) && DECL_DECOMPOSITION_P (exp))
> +      if (VAR_P (exp)
> +	  && (DECL_DECOMPOSITION_P (exp)
> +	      || (flag_array_copy
> +		  && TREE_CODE (tree_strip_any_location_wrapper (init))
> +		     != STRING_CST)))
>   	{
>   	  from_array = 1;
>   	  init = mark_rvalue_use (init);
> diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
> index cb20329ceb5..8e96a925186 100644
> --- a/gcc/cp/typeck.c
> +++ b/gcc/cp/typeck.c
> @@ -9144,11 +9144,12 @@ cp_build_modify_expr (location_t loc, tree lhs, enum tree_code modifycode,
>   	}
>   
>         /* Allow array assignment in compiler-generated code.  */
> -      else if (!current_function_decl
> -	       || !DECL_DEFAULTED_FN (current_function_decl))
> +      else if ((!current_function_decl
> +		|| !DECL_DEFAULTED_FN (current_function_decl))
> +	       && !flag_array_copy)
>   	{
> -          /* This routine is used for both initialization and assignment.
> -             Make sure the diagnostic message differentiates the context.  */
> +	  /* This routine is used for both initialization and assignment.
> +	     Make sure the diagnostic message differentiates the context.  */
>   	  if (complain & tf_error)
>   	    {
>   	      if (modifycode == INIT_EXPR)
> diff --git a/gcc/cp/typeck2.c b/gcc/cp/typeck2.c
> index e98fbf7f5fa..20d46716279 100644
> --- a/gcc/cp/typeck2.c
> +++ b/gcc/cp/typeck2.c
> @@ -1175,6 +1175,9 @@ digest_init_r (tree type, tree init, int nested, int flags,
>   	    }
>   	  return init;
>   	}
> +      if (flag_array_copy
> +	  && same_type_ignoring_top_level_qualifiers_p(type, TREE_TYPE (init)))
> +	return build_array_copy(init);

Missing space before ( on two lines.

>       }
>   
>     /* Handle scalar types (including conversions) and references.  */
> @@ -1244,13 +1247,18 @@ digest_init_r (tree type, tree init, int nested, int flags,
>   				     complain);
>     else
>       {
> -      if (COMPOUND_LITERAL_P (stripped_init) && code == ARRAY_TYPE)
> +      if (COMPOUND_LITERAL_P (stripped_init) && code == ARRAY_TYPE
> +	  && (complain & tf_error))
>   	{
> -	  if (complain & tf_error)
> -	    error_at (loc, "cannot initialize aggregate of type %qT with "
> -		      "a compound literal", type);
> -
> -	  return error_mark_node;
> +	  if (flag_array_copy)
> +	    pedwarn (loc, OPT_Wpedantic, "ISO C++ doesn%'t allow initialization"
> +		     "of an array of type %qT with a compound literal", type);
> +	  else
> +	    {
> +	      error_at (loc, "cannot initialize aggregate of type %qT with "
> +			     "a compound literal", type);
> +	      return error_mark_node;
> +	    }

This changes the function to only return error_mark_node when we're 
diagnosing errors, which seems wrong; we need it to fail in SFINAE 
context as well.

>   	}
>   
>         if (code == ARRAY_TYPE
> @@ -1265,8 +1273,14 @@ digest_init_r (tree type, tree init, int nested, int flags,
>   	    return init;
>   
>   	  if (complain & tf_error)
> -	    error_at (loc, "array must be initialized with a brace-enclosed"
> -		      " initializer");
> +	  {
> +	    if (!flag_array_copy)
> +	      error_at (loc, "array must be initialized with a brace-enclosed"
> +			     " initializer");
> +	    else
> +	      error_at (loc, "array must be initialized with a brace-enclosed"
> +			     " initializer or an array value of the same type");
> +	    }
>   	  return error_mark_node;
>   	}
>   
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index d62ec08150e..32a34ca74bb 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -213,6 +213,7 @@ in the following sections.
>   @xref{C++ Dialect Options,,Options Controlling C++ Dialect}.
>   @gccoptlist{-fabi-version=@var{n}  -fno-access-control @gol
>   -faligned-new=@var{n}  -fargs-in-order=@var{n}  -fchar8_t  -fcheck-new @gol
> +-farray-copy @gol
>   -fconstexpr-depth=@var{n}  -fconstexpr-cache-depth=@var{n} @gol
>   -fconstexpr-loop-limit=@var{n}  -fconstexpr-ops-limit=@var{n} @gol
>   -fno-elide-constructors @gol
> @@ -3001,6 +3002,11 @@ return value even without this option.  In all other cases, when
>   exhaustion is signalled by throwing @code{std::bad_alloc}.  See also
>   @samp{new (nothrow)}.
>   
> +@item -farray-copy
> +@opindex farray-copy
> +Enables experimental support for P1997; C array copy semantics, pseudo-
> +destructors and auto placeholder for array element type.
> +
>   @item -fconcepts
>   @itemx -fconcepts-ts
>   @opindex fconcepts
> diff --git a/gcc/testsuite/g++.dg/init/array-copy1.C b/gcc/testsuite/g++.dg/init/array-copy1.C
> new file mode 100644
> index 00000000000..15986a00d1b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/init/array-copy1.C
> @@ -0,0 +1,66 @@
> +// PR c++/103238 (array-copy extensions for P1997)
> +// Array variable initialization nocompile tests -
> +// should give same errors before and after the array-copy patch.
> +
> +// { dg-do compile { target c++11 } } c++11 for uniform init
> +
> +typedef int int2[2];
> +int2 u = {11,66};
> +
> +int* p = new int2{u}; // { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." }
> +
> +// Initializations from from rvalue array
> +int2 g = int2{11,66};
> +int2 j ( int2{11,66} );  // takes P0960 paren-init path in c++20
> +int2 k = { int2{11,66} };
> +int2 l { int2{11,66} };
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-4 }
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-4 }
> +// { dg-error "taking address of temporary array" "address of temporary" { target *-*-* } .-4 }
> +// { dg-error "taking address of temporary array" "address of temporary" { target *-*-* } .-4 }
> +
> +// Initializations from from lvalue array
> +int2 gc = u;
> +int2 jc (u);  // takes P0960 paren-init path in c++20
> +int2 kc = {u};
> +int2 lc {u};
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-4 }
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-4 }
> +// { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." "bad conv" { target *-*-* } .-4 }
> +// { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." "bad conv" { target *-*-* } .-4 }
> +
> +char str[] = "str";
> +
> +char cpy[] = str;
> +char cpp[] (str); // takes P0960 paren-init path in c++20
> +char cpu[] = {str};
> +char cpw[] {str};
> +// { dg-error "initializer fails to determine size of .cpy." "deduce size" { target *-*-*  } .-4 }
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-5 }
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target c++17_down } .-5 }
> +// { dg-error "initializer fails to determine size of .cpp." "deduce size" { target c++17_down  } .-6 }
> +// { dg-error "invalid conversion from .char\\*. to .char. .\\-fpermissive." "bad conv" { target c++20 } .-7 }
> +// { dg-error "invalid conversion from .char\\*. to .char. .\\-fpermissive." "bad conv" { target *-*-* } .-7 }
> +// { dg-error "invalid conversion from .char\\*. to .char. .\\-fpermissive." "bad conv" { target *-*-* } .-7 }
> +
> +char cp5[5] = str;
> +char cp3[3] = str;
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-2 }
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-2 }
> +
> +int2 s[2] = { u };
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-1 }
> +int2 x[2] = { u
> +	    , u };
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-2 }
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-2 }
> +int2 y[2] = { 1,2
> +	    , {u} };
> +// { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." "bad conv" { target *-*-* } .-1 }
> +int2 z[2] = { {u},
> +	       3,{4} };
> +// { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." "bad conv" { target *-*-* } .-2 }
> +
> +__UINT8_TYPE__ m222[2][2][2] {0,1,2,3,4,5,6,7};
> +__UINT8_TYPE__ c222[2][2][2] { m222[0], m222[1][0], 6,7 };
> +// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-1 }
> diff --git a/gcc/testsuite/g++.dg/init/array-copy2.C b/gcc/testsuite/g++.dg/init/array-copy2.C
> new file mode 100644
> index 00000000000..7e1ad23592b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/init/array-copy2.C
> @@ -0,0 +1,68 @@
> +// PR c++/103238 (array-copy extensions for P1997)
> +// Array variable initialization compile tests, -farray-copy enabled
> +// (c.f. array-copy1.C for compile fail tests)
> +
> +// { dg-do compile { target c++11 } }  c++11 for uniform init
> +// { dg-additional-options "-farray-copy" }
> +
> +typedef int int2[2];
> +int2 u = {11,66};
> +
> +int* p = new int2{u};
> +
> +// Initializations from from rvalue array
> +int2 g = int2{11,66};
> +int2 j ( int2{11,66} );  // takes P0960 paren-init path in c++20
> +int2 k = { int2{11,66} };
> +int2 l { int2{11,66} };
> +
> +// Initializations from from lvalue array
> +int2 gc = u;
> +int2 jc (u);  // takes P0960 paren-init path in c++20
> +int2 kc = {u};
> +int2 lc {u};
> +
> +char str[] = "str";
> +
> +char cpy[] = str;
> +char cpp[] (str);  // takes P0960 paren-init path in c++20
> +char cpu[] = {str};
> +char cpw[] {str};
> +
> +char cp5[5] = str;
> +char cp3[3] = str;
> +// { dg-error "array of type .char .5.. cannot be initialized with an array of type .char .4.." "" { target *-*-* } .-2 }
> +// { dg-error "array of type .char .3.. cannot be initialized with an array of type .char .4.." "" { target *-*-* } .-2 }
> +
> +constexpr char cstr[] = "str";
> +constexpr char ccpy[] = cstr;
> +static_assert(
> +      __builtin_bit_cast(__UINT32_TYPE__, ccpy)
> +   == __builtin_bit_cast(__UINT32_TYPE__, "str")
> +, ""
> +);
> +
> +constexpr __UINT32_TYPE__ ci[] = {11,66};
> +constexpr __UINT32_TYPE__ cc[] = ci;
> +static_assert(
> +      __builtin_bit_cast(__UINT64_TYPE__, ci)
> +   == __builtin_bit_cast(__UINT64_TYPE__, cc)
> +, ""
> +);
> +
> +int2 s[2] = { u };
> +int2 x[2] = { u
> +	    , u };
> +int2 y[2] = { 1,2
> +	    , {u} };
> +int2 z[2] = { {u}
> +	   , 3,{4} };
> +
> +__UINT8_TYPE__ m222[2][2][2] {0,1,2,3,4,5,6,7};
> +__UINT8_TYPE__ c222[2][2][2] { m222[0], m222[1][0], 6,7 };
> +
> +int main() {
> +  if (__builtin_bit_cast(__UINT64_TYPE__, m222)
> +   != __builtin_bit_cast(__UINT64_TYPE__, c222))
> +      __builtin_abort ();
> +}
> diff --git a/gcc/testsuite/g++.dg/init/array-copy3.C b/gcc/testsuite/g++.dg/init/array-copy3.C
> new file mode 100644
> index 00000000000..bec78cb8435
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/init/array-copy3.C
> @@ -0,0 +1,41 @@
> +// PR c++/103238 (array-copy extensions for P1997)
> +// Array member initialization nocompile tests -
> +// should give same errors before and after the array-copy patch.
> +
> +// { dg-do compile { target c++11 } } c++11 for uniform init
> +//
> +
> +typedef int int2[2];
> +
> +struct A { int2 a; };
> +
> +A ad { int2{11,66} }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +A ai = { int2{11,66} }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +
> +struct B { int2 a, b, c, d; };
> +
> +constexpr
> +B b {
> +    {11,66}
> +  , {b.a} // { dg-error "invalid conversion from .const int\\*. to .int. .\\-fpermissive." }
> +  , (b.b) // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +  ,  b.c
> +};
> +
> +struct C {
> +  int2 a, b, c;
> +  C(const int2& r = {11,66})
> +   : a{r} // { dg-error "invalid conversion from .const int\\*. to .int. .\\-fpermissive." }
> +   , b(a) // { dg-error "array used as initializer" }
> +   , c{b} // { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." }
> +     {}
> +};
> +
> +// Not a test; a workaround to construct a single array member from array
> +struct D {
> +  int2 d;
> +  constexpr D(const int2& r = {})
> +   : D{__builtin_bit_cast(D,r)} {}
> +};
> +constexpr D d{{11,66}};
> +constexpr D e{b.a};
> diff --git a/gcc/testsuite/g++.dg/init/array-copy4.C b/gcc/testsuite/g++.dg/init/array-copy4.C
> new file mode 100644
> index 00000000000..8db2852011a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/init/array-copy4.C
> @@ -0,0 +1,42 @@
> +// PR c++/103238 (array-copy extensions for P1997)
> +// Array member initialization compile tests -
> +// (c.f. array-copy3.C for compile fail tests)
> +
> +// { dg-do compile { target c++11 } } c++11 for uniform init
> +// { dg-additional-options "-farray-copy" }
> +
> +typedef int int2[2];
> +
> +struct A { int2 a; };
> +
> +A ad { int2{11,66} };
> +A ai = { int2{11,66} };
> +
> +struct B { int2 a, b, c, d; };
> +
> +constexpr
> +B b {
> +    {11,66}
> +  , {b.a}
> +  , (b.b)
> +  ,  b.c
> +};
> +
> +struct C {
> +  int2 a, b, c;
> +  C(const int2& r = {11,66})
> +   : a{r}
> +   , b(a)
> +   , c{b}
> +     {}
> +};
> +
> +template<class T>
> +constexpr int equal(T const& a, T const& b) {
> +  using bytes = unsigned char[sizeof(T)];
> +  return __builtin_memcmp(
> +         __builtin_bit_cast(bytes,a),
> +         __builtin_bit_cast(bytes,b),
> +         sizeof(T)) == 0;
> +}
> +static_assert( equal(b, B{{11,66},{11,66},{11,66},{11,66}}), "");
> diff --git a/gcc/testsuite/g++.dg/init/array-copy5.C b/gcc/testsuite/g++.dg/init/array-copy5.C
> new file mode 100644
> index 00000000000..a3b6c275f2b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/init/array-copy5.C
> @@ -0,0 +1,36 @@
> +// PR c++/103238 (array-copy extensions for P1997)
> +// Array member initialization nocompile tests, nsdmi and designated init
> +// should give same errors before and after the array-copy patch.
> +
> +// { dg-do compile { target c++14 } } c++14 for aggregate with member inits
> +// { dg-options "" }
> +//
> +
> +typedef int int2[2];
> +
> +struct A { int2 a = int2{11,66}; }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +
> +constexpr
> +A a { .a = int2{11,66} }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +A y = { .a{int2{11,66}} }; // { dg-error "taking address of temporary array" }
> +
> +struct B {
> +  int2 a = {11,66};
> +  int2 b = a; // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +  int2 c = {b}; // { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." }
> +  int2 d {c}; // { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." }
> +};
> +
> +constexpr
> +B b {
> +    {11,66}
> +  , {b.a} // { dg-error "invalid conversion from .const int\\*. to .int. .\\-fpermissive." }
> +  , (b.b) // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +  ,  b.c
> +};
> +
> +B bd { .a{11,66}, .b = bd.a, .c {bd.b}, .d = {bd.c} }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +
> +struct D : A {
> +  B b {.a{11,66}, .b = b.a, .c {b.b}, .d = {b.c}}; // { dg-error "array must be initialized with a brace-enclosed initializer" }
> +};
> diff --git a/gcc/testsuite/g++.dg/init/array-copy6.C b/gcc/testsuite/g++.dg/init/array-copy6.C
> new file mode 100644
> index 00000000000..09b7478e07f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/init/array-copy6.C
> @@ -0,0 +1,51 @@
> +// PR c++/103238 (array-copy extensions for P1997)
> +// Array member initialization compile tests, nsdmi and designated init
> +// (c.f. array-copy5.C for compile fail tests)
> +
> +// { dg-do compile { target c++20 } }
> +//
> +// { dg-additional-options "-farray-copy" }
> +
> +typedef int int2[2];
> +
> +struct A { int2 a = int2{11,66}; };
> +
> +constexpr
> +A a { .a = int2{11,66} };
> +A y = { .a{int2{11,66}} };
> +
> +struct B {
> +  int2 a = {11,66};
> +  int2 b = a;
> +  int2 c = {b};
> +  int2 d {c};
> +};
> +
> +constexpr
> +B b {
> +    {11,66}
> +  , {b.a}
> +  , (b.b)
> +  ,  b.c
> +};
> +
> +B bd { .a{11,66}, .b = bd.a, .c {bd.b}, .d = {bd.c} };
> +
> +struct D : A {
> +  B b {.a{11,66}, .b = b.a, .c {b.b}, .d = {b.c}};
> +};
> +
> +constexpr
> +D d;
> +
> +template<class T>
> +constexpr int equal(T const& a, T const& b) {
> +  using bytes = unsigned char[sizeof(T)];
> +  return __builtin_memcmp(
> +         __builtin_bit_cast(bytes,a),
> +         __builtin_bit_cast(bytes,b),
> +         sizeof(T)) == 0;
> +}
> +static_assert( equal(b, B{}), "");
> +static_assert( equal(d, D{}), "");
> +static_assert( equal(d, D{a,b}), "");
>
  

Patch

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 86c007f53de..fb0b1ef294f 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -6796,6 +6796,11 @@  complete_array_type (tree *ptype, tree initial_value, bool do_default)
 	    = int_size_in_bytes (TREE_TYPE (TREE_TYPE (initial_value)));
 	  maxindex = size_int (TREE_STRING_LENGTH (initial_value)/eltsize - 1);
 	}
+      else if (flag_array_copy
+	       && TREE_CODE (TREE_TYPE (initial_value)) == ARRAY_TYPE)
+	{
+	  maxindex = array_type_nelts (TREE_TYPE (initial_value));
+	}
       else if (TREE_CODE (initial_value) == CONSTRUCTOR)
 	{
 	  vec<constructor_elt, va_gc> *v = CONSTRUCTOR_ELTS (initial_value);
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 3976fc368db..9d2a6ad8e1c 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1579,6 +1579,10 @@  fcilkplus
 C ObjC C++ ObjC++ LTO Undocumented Ignore
 Removed in GCC 8.  This switch has no effect.
 
+farray-copy
+C ObjC C++ ObjC++ Var(flag_array_copy)
+Enable experimental extension for P1997; C array copy semantics.
+
 fconcepts
 C++ ObjC++ Var(flag_concepts)
 Enable support for C++ concepts.
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 83a2d3bf8f1..1010fa2c53f 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -5834,15 +5834,19 @@  tree
 do_aggregate_paren_init (tree init, tree type)
 {
   tree val = TREE_VALUE (init);
+  tree stripped_val = tree_strip_any_location_wrapper (val);
 
   if (TREE_CHAIN (init) == NULL_TREE)
     {
       /* If the list has a single element and it's a string literal,
 	 then it's the initializer for the array as a whole.  */
-      if (TREE_CODE (type) == ARRAY_TYPE
-	  && char_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (type)))
-	  && TREE_CODE (tree_strip_any_location_wrapper (val))
-	     == STRING_CST)
+      if ((TREE_CODE (type) == ARRAY_TYPE
+	   && char_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (type)))
+	   && TREE_CODE (stripped_val) == STRING_CST)
+	  || (flag_array_copy
+	      && TREE_CODE (TREE_TYPE (stripped_val)) == ARRAY_TYPE
+	      && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (type),
+					TREE_TYPE (TREE_TYPE (stripped_val)))))
 	return val;
       /* Handle non-standard extensions like compound literals.  This also
 	 prevents triggering aggregate parenthesized-initialization in
@@ -6007,9 +6011,10 @@  maybe_deduce_size_from_array_init (tree decl, tree init)
 {
   tree type = TREE_TYPE (decl);
 
-  if (TREE_CODE (type) == ARRAY_TYPE
-      && TYPE_DOMAIN (type) == NULL_TREE
-      && TREE_CODE (decl) != TYPE_DECL)
+  if (TREE_CODE (type) != ARRAY_TYPE
+      || TREE_CODE (decl) == TYPE_DECL)
+    ;
+  else if (TYPE_DOMAIN (type) == NULL_TREE)
     {
       /* do_default is really a C-ism to deal with tentative definitions.
 	 But let's leave it here to ease the eventual merge.  */
@@ -6072,6 +6077,17 @@  maybe_deduce_size_from_array_init (tree decl, tree init)
 
       relayout_decl (decl);
     }
+  else if (flag_array_copy && init
+	   && TREE_CODE (TREE_TYPE (init)) == ARRAY_TYPE
+	   && TREE_CODE (tree_strip_any_location_wrapper (init)) != STRING_CST
+	   && !same_type_ignoring_top_level_qualifiers_p (type,
+							  TREE_TYPE (init)))
+    {
+      error_at (DECL_SOURCE_LOCATION (decl),
+		"array of type %qT cannot be initialized with an "
+		"array of type %qT in %qD", type, TREE_TYPE (init), decl);
+      TREE_TYPE (decl) = error_mark_node;
+    }
 }
 
 /* Set DECL_SIZE, DECL_ALIGN, etc. for DECL (a VAR_DECL), and issue
@@ -6815,21 +6831,23 @@  reshape_init_r (tree type, reshape_iter *d, tree first_initializer_p,
       return init;
     }
 
-  /* [dcl.init.string]
+  /* [dcl.init.string] or flag_array_copy extension P1997
 
       A char array (whether plain char, signed char, or unsigned char)
       can be initialized by a string-literal (optionally enclosed in
       braces); a wchar_t array can be initialized by a wide
-      string-literal (optionally enclosed in braces).  */
-  if (TREE_CODE (type) == ARRAY_TYPE
-      && char_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (type))))
+      string-literal (optionally enclosed in braces).
+      With flag_array_copy an array can be initialized by an array value
+      (optionally enclosed in braces).  */
+  if (TREE_CODE (type) == ARRAY_TYPE)
     {
       tree arr_init = init;
       tree stripped_arr_init = stripped_init;
       reshape_iter stripd = {};
 
-      /* Strip one level of braces if and only if they enclose a single
-	 element (as allowed by [dcl.init.string]).  */
+      /* Strip one level of braces if they enclose a single element,
+	 provisionally for elison in the optionally-braced case if the
+	 initializer is of compatible array type, as checked next. */
       if (!first_initializer_p
 	  && TREE_CODE (stripped_arr_init) == CONSTRUCTOR
 	  && CONSTRUCTOR_NELTS (stripped_arr_init) == 1)
@@ -6839,10 +6857,19 @@  reshape_init_r (tree type, reshape_iter *d, tree first_initializer_p,
 	  stripped_arr_init = tree_strip_any_location_wrapper (arr_init);
 	}
 
-      /* If it's a string literal, then it's the initializer for the array
-	 as a whole. Otherwise, continue with normal initialization for
-	 array types (one value per array element).  */
-      if (TREE_CODE (stripped_arr_init) == STRING_CST)
+      tree init_type = TREE_TYPE (stripped_arr_init);
+
+      /* If it's a string literal initializer for a char array target,
+	 or, with flag_array_copy, an array value of same element type,
+	 then it's the initializer for the array as a whole.
+	 Else, continue with element by element array initialization.  */
+      if ((TREE_CODE (stripped_arr_init) == STRING_CST
+	   && char_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (type))))
+	  || (flag_array_copy
+	      && TREE_CODE (init_type) == ARRAY_TYPE
+	      && TYPE_DOMAIN (init_type) != NULL_TREE
+	      && same_type_ignoring_top_level_qualifiers_p (
+				TREE_TYPE (type), TREE_TYPE (init_type))))
 	{
 	  if ((first_initializer_p && has_designator_problem (d, complain))
 	      || (stripd.cur && has_designator_problem (&stripd, complain)))
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index 3ba2e3bbe04..fd9a065ed95 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -1787,7 +1787,11 @@  build_aggr_init (tree exp, tree init, int flags, tsubst_flags_t complain)
       tree itype = init ? TREE_TYPE (init) : NULL_TREE;
       int from_array = 0;
 
-      if (VAR_P (exp) && DECL_DECOMPOSITION_P (exp))
+      if (VAR_P (exp)
+	  && (DECL_DECOMPOSITION_P (exp)
+	      || (flag_array_copy
+		  && TREE_CODE (tree_strip_any_location_wrapper (init))
+		     != STRING_CST)))
 	{
 	  from_array = 1;
 	  init = mark_rvalue_use (init);
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index cb20329ceb5..8e96a925186 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -9144,11 +9144,12 @@  cp_build_modify_expr (location_t loc, tree lhs, enum tree_code modifycode,
 	}
 
       /* Allow array assignment in compiler-generated code.  */
-      else if (!current_function_decl
-	       || !DECL_DEFAULTED_FN (current_function_decl))
+      else if ((!current_function_decl
+		|| !DECL_DEFAULTED_FN (current_function_decl))
+	       && !flag_array_copy)
 	{
-          /* This routine is used for both initialization and assignment.
-             Make sure the diagnostic message differentiates the context.  */
+	  /* This routine is used for both initialization and assignment.
+	     Make sure the diagnostic message differentiates the context.  */
 	  if (complain & tf_error)
 	    {
 	      if (modifycode == INIT_EXPR)
diff --git a/gcc/cp/typeck2.c b/gcc/cp/typeck2.c
index e98fbf7f5fa..20d46716279 100644
--- a/gcc/cp/typeck2.c
+++ b/gcc/cp/typeck2.c
@@ -1175,6 +1175,9 @@  digest_init_r (tree type, tree init, int nested, int flags,
 	    }
 	  return init;
 	}
+      if (flag_array_copy
+	  && same_type_ignoring_top_level_qualifiers_p(type, TREE_TYPE (init)))
+	return build_array_copy(init);
     }
 
   /* Handle scalar types (including conversions) and references.  */
@@ -1244,13 +1247,18 @@  digest_init_r (tree type, tree init, int nested, int flags,
 				     complain);
   else
     {
-      if (COMPOUND_LITERAL_P (stripped_init) && code == ARRAY_TYPE)
+      if (COMPOUND_LITERAL_P (stripped_init) && code == ARRAY_TYPE
+	  && (complain & tf_error))
 	{
-	  if (complain & tf_error)
-	    error_at (loc, "cannot initialize aggregate of type %qT with "
-		      "a compound literal", type);
-
-	  return error_mark_node;
+	  if (flag_array_copy)
+	    pedwarn (loc, OPT_Wpedantic, "ISO C++ doesn%'t allow initialization"
+		     "of an array of type %qT with a compound literal", type);
+	  else
+	    {
+	      error_at (loc, "cannot initialize aggregate of type %qT with "
+			     "a compound literal", type);
+	      return error_mark_node;
+	    }
 	}
 
       if (code == ARRAY_TYPE
@@ -1265,8 +1273,14 @@  digest_init_r (tree type, tree init, int nested, int flags,
 	    return init;
 
 	  if (complain & tf_error)
-	    error_at (loc, "array must be initialized with a brace-enclosed"
-		      " initializer");
+	  {
+	    if (!flag_array_copy)
+	      error_at (loc, "array must be initialized with a brace-enclosed"
+			     " initializer");
+	    else
+	      error_at (loc, "array must be initialized with a brace-enclosed"
+			     " initializer or an array value of the same type");
+	    }
 	  return error_mark_node;
 	}
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d62ec08150e..32a34ca74bb 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -213,6 +213,7 @@  in the following sections.
 @xref{C++ Dialect Options,,Options Controlling C++ Dialect}.
 @gccoptlist{-fabi-version=@var{n}  -fno-access-control @gol
 -faligned-new=@var{n}  -fargs-in-order=@var{n}  -fchar8_t  -fcheck-new @gol
+-farray-copy @gol
 -fconstexpr-depth=@var{n}  -fconstexpr-cache-depth=@var{n} @gol
 -fconstexpr-loop-limit=@var{n}  -fconstexpr-ops-limit=@var{n} @gol
 -fno-elide-constructors @gol
@@ -3001,6 +3002,11 @@  return value even without this option.  In all other cases, when
 exhaustion is signalled by throwing @code{std::bad_alloc}.  See also
 @samp{new (nothrow)}.
 
+@item -farray-copy
+@opindex farray-copy
+Enables experimental support for P1997; C array copy semantics, pseudo-
+destructors and auto placeholder for array element type.
+
 @item -fconcepts
 @itemx -fconcepts-ts
 @opindex fconcepts
diff --git a/gcc/testsuite/g++.dg/init/array-copy1.C b/gcc/testsuite/g++.dg/init/array-copy1.C
new file mode 100644
index 00000000000..15986a00d1b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array-copy1.C
@@ -0,0 +1,66 @@ 
+// PR c++/103238 (array-copy extensions for P1997)
+// Array variable initialization nocompile tests -
+// should give same errors before and after the array-copy patch.
+
+// { dg-do compile { target c++11 } } c++11 for uniform init
+
+typedef int int2[2];
+int2 u = {11,66};
+
+int* p = new int2{u}; // { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." }
+
+// Initializations from from rvalue array
+int2 g = int2{11,66};
+int2 j ( int2{11,66} );  // takes P0960 paren-init path in c++20
+int2 k = { int2{11,66} };
+int2 l { int2{11,66} };
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-4 }
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-4 }
+// { dg-error "taking address of temporary array" "address of temporary" { target *-*-* } .-4 }
+// { dg-error "taking address of temporary array" "address of temporary" { target *-*-* } .-4 }
+
+// Initializations from from lvalue array
+int2 gc = u;
+int2 jc (u);  // takes P0960 paren-init path in c++20
+int2 kc = {u};
+int2 lc {u};
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-4 }
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-4 }
+// { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." "bad conv" { target *-*-* } .-4 }
+// { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." "bad conv" { target *-*-* } .-4 }
+
+char str[] = "str";
+
+char cpy[] = str;
+char cpp[] (str); // takes P0960 paren-init path in c++20
+char cpu[] = {str};
+char cpw[] {str};
+// { dg-error "initializer fails to determine size of .cpy." "deduce size" { target *-*-*  } .-4 }
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-5 }
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target c++17_down } .-5 }
+// { dg-error "initializer fails to determine size of .cpp." "deduce size" { target c++17_down  } .-6 }
+// { dg-error "invalid conversion from .char\\*. to .char. .\\-fpermissive." "bad conv" { target c++20 } .-7 }
+// { dg-error "invalid conversion from .char\\*. to .char. .\\-fpermissive." "bad conv" { target *-*-* } .-7 }
+// { dg-error "invalid conversion from .char\\*. to .char. .\\-fpermissive." "bad conv" { target *-*-* } .-7 }
+
+char cp5[5] = str;
+char cp3[3] = str;
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-2 }
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-2 }
+
+int2 s[2] = { u };
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-1 }
+int2 x[2] = { u
+	    , u };
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-2 }
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-2 }
+int2 y[2] = { 1,2
+	    , {u} };
+// { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." "bad conv" { target *-*-* } .-1 }
+int2 z[2] = { {u},
+	       3,{4} };
+// { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." "bad conv" { target *-*-* } .-2 }
+
+__UINT8_TYPE__ m222[2][2][2] {0,1,2,3,4,5,6,7};
+__UINT8_TYPE__ c222[2][2][2] { m222[0], m222[1][0], 6,7 };
+// { dg-error "array must be initialized with a brace-enclosed initializer" "array init" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/init/array-copy2.C b/gcc/testsuite/g++.dg/init/array-copy2.C
new file mode 100644
index 00000000000..7e1ad23592b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array-copy2.C
@@ -0,0 +1,68 @@ 
+// PR c++/103238 (array-copy extensions for P1997)
+// Array variable initialization compile tests, -farray-copy enabled
+// (c.f. array-copy1.C for compile fail tests)
+
+// { dg-do compile { target c++11 } }  c++11 for uniform init
+// { dg-additional-options "-farray-copy" }
+
+typedef int int2[2];
+int2 u = {11,66};
+
+int* p = new int2{u};
+
+// Initializations from from rvalue array
+int2 g = int2{11,66};
+int2 j ( int2{11,66} );  // takes P0960 paren-init path in c++20
+int2 k = { int2{11,66} };
+int2 l { int2{11,66} };
+
+// Initializations from from lvalue array
+int2 gc = u;
+int2 jc (u);  // takes P0960 paren-init path in c++20
+int2 kc = {u};
+int2 lc {u};
+
+char str[] = "str";
+
+char cpy[] = str;
+char cpp[] (str);  // takes P0960 paren-init path in c++20
+char cpu[] = {str};
+char cpw[] {str};
+
+char cp5[5] = str;
+char cp3[3] = str;
+// { dg-error "array of type .char .5.. cannot be initialized with an array of type .char .4.." "" { target *-*-* } .-2 }
+// { dg-error "array of type .char .3.. cannot be initialized with an array of type .char .4.." "" { target *-*-* } .-2 }
+
+constexpr char cstr[] = "str";
+constexpr char ccpy[] = cstr;
+static_assert(
+      __builtin_bit_cast(__UINT32_TYPE__, ccpy)
+   == __builtin_bit_cast(__UINT32_TYPE__, "str")
+, ""
+);
+
+constexpr __UINT32_TYPE__ ci[] = {11,66};
+constexpr __UINT32_TYPE__ cc[] = ci;
+static_assert(
+      __builtin_bit_cast(__UINT64_TYPE__, ci)
+   == __builtin_bit_cast(__UINT64_TYPE__, cc)
+, ""
+);
+
+int2 s[2] = { u };
+int2 x[2] = { u
+	    , u };
+int2 y[2] = { 1,2
+	    , {u} };
+int2 z[2] = { {u}
+	   , 3,{4} };
+
+__UINT8_TYPE__ m222[2][2][2] {0,1,2,3,4,5,6,7};
+__UINT8_TYPE__ c222[2][2][2] { m222[0], m222[1][0], 6,7 };
+
+int main() {
+  if (__builtin_bit_cast(__UINT64_TYPE__, m222)
+   != __builtin_bit_cast(__UINT64_TYPE__, c222))
+      __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/init/array-copy3.C b/gcc/testsuite/g++.dg/init/array-copy3.C
new file mode 100644
index 00000000000..bec78cb8435
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array-copy3.C
@@ -0,0 +1,41 @@ 
+// PR c++/103238 (array-copy extensions for P1997)
+// Array member initialization nocompile tests -
+// should give same errors before and after the array-copy patch.
+
+// { dg-do compile { target c++11 } } c++11 for uniform init
+//
+
+typedef int int2[2];
+
+struct A { int2 a; };
+
+A ad { int2{11,66} }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
+A ai = { int2{11,66} }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
+
+struct B { int2 a, b, c, d; };
+
+constexpr
+B b {
+    {11,66}
+  , {b.a} // { dg-error "invalid conversion from .const int\\*. to .int. .\\-fpermissive." }
+  , (b.b) // { dg-error "array must be initialized with a brace-enclosed initializer" }
+  ,  b.c
+};
+
+struct C {
+  int2 a, b, c;
+  C(const int2& r = {11,66})
+   : a{r} // { dg-error "invalid conversion from .const int\\*. to .int. .\\-fpermissive." }
+   , b(a) // { dg-error "array used as initializer" }
+   , c{b} // { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." }
+     {}
+};
+
+// Not a test; a workaround to construct a single array member from array
+struct D {
+  int2 d;
+  constexpr D(const int2& r = {})
+   : D{__builtin_bit_cast(D,r)} {}
+};
+constexpr D d{{11,66}};
+constexpr D e{b.a};
diff --git a/gcc/testsuite/g++.dg/init/array-copy4.C b/gcc/testsuite/g++.dg/init/array-copy4.C
new file mode 100644
index 00000000000..8db2852011a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array-copy4.C
@@ -0,0 +1,42 @@ 
+// PR c++/103238 (array-copy extensions for P1997)
+// Array member initialization compile tests -
+// (c.f. array-copy3.C for compile fail tests)
+
+// { dg-do compile { target c++11 } } c++11 for uniform init
+// { dg-additional-options "-farray-copy" }
+
+typedef int int2[2];
+
+struct A { int2 a; };
+
+A ad { int2{11,66} };
+A ai = { int2{11,66} };
+
+struct B { int2 a, b, c, d; };
+
+constexpr
+B b {
+    {11,66}
+  , {b.a}
+  , (b.b)
+  ,  b.c
+};
+
+struct C {
+  int2 a, b, c;
+  C(const int2& r = {11,66})
+   : a{r}
+   , b(a)
+   , c{b}
+     {}
+};
+
+template<class T>
+constexpr int equal(T const& a, T const& b) {
+  using bytes = unsigned char[sizeof(T)];
+  return __builtin_memcmp(
+         __builtin_bit_cast(bytes,a),
+         __builtin_bit_cast(bytes,b),
+         sizeof(T)) == 0;
+}
+static_assert( equal(b, B{{11,66},{11,66},{11,66},{11,66}}), "");
diff --git a/gcc/testsuite/g++.dg/init/array-copy5.C b/gcc/testsuite/g++.dg/init/array-copy5.C
new file mode 100644
index 00000000000..a3b6c275f2b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array-copy5.C
@@ -0,0 +1,36 @@ 
+// PR c++/103238 (array-copy extensions for P1997)
+// Array member initialization nocompile tests, nsdmi and designated init
+// should give same errors before and after the array-copy patch.
+
+// { dg-do compile { target c++14 } } c++14 for aggregate with member inits
+// { dg-options "" }
+//
+
+typedef int int2[2];
+
+struct A { int2 a = int2{11,66}; }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
+
+constexpr
+A a { .a = int2{11,66} }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
+A y = { .a{int2{11,66}} }; // { dg-error "taking address of temporary array" }
+
+struct B {
+  int2 a = {11,66};
+  int2 b = a; // { dg-error "array must be initialized with a brace-enclosed initializer" }
+  int2 c = {b}; // { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." }
+  int2 d {c}; // { dg-error "invalid conversion from .int\\*. to .int. .\\-fpermissive." }
+};
+
+constexpr
+B b {
+    {11,66}
+  , {b.a} // { dg-error "invalid conversion from .const int\\*. to .int. .\\-fpermissive." }
+  , (b.b) // { dg-error "array must be initialized with a brace-enclosed initializer" }
+  ,  b.c
+};
+
+B bd { .a{11,66}, .b = bd.a, .c {bd.b}, .d = {bd.c} }; // { dg-error "array must be initialized with a brace-enclosed initializer" }
+
+struct D : A {
+  B b {.a{11,66}, .b = b.a, .c {b.b}, .d = {b.c}}; // { dg-error "array must be initialized with a brace-enclosed initializer" }
+};
diff --git a/gcc/testsuite/g++.dg/init/array-copy6.C b/gcc/testsuite/g++.dg/init/array-copy6.C
new file mode 100644
index 00000000000..09b7478e07f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array-copy6.C
@@ -0,0 +1,51 @@ 
+// PR c++/103238 (array-copy extensions for P1997)
+// Array member initialization compile tests, nsdmi and designated init
+// (c.f. array-copy5.C for compile fail tests)
+
+// { dg-do compile { target c++20 } }
+//
+// { dg-additional-options "-farray-copy" }
+
+typedef int int2[2];
+
+struct A { int2 a = int2{11,66}; };
+
+constexpr
+A a { .a = int2{11,66} };
+A y = { .a{int2{11,66}} };
+
+struct B {
+  int2 a = {11,66};
+  int2 b = a;
+  int2 c = {b};
+  int2 d {c};
+};
+
+constexpr
+B b {
+    {11,66}
+  , {b.a}
+  , (b.b)
+  ,  b.c
+};
+
+B bd { .a{11,66}, .b = bd.a, .c {bd.b}, .d = {bd.c} };
+
+struct D : A {
+  B b {.a{11,66}, .b = b.a, .c {b.b}, .d = {b.c}};
+};
+
+constexpr
+D d;
+
+template<class T>
+constexpr int equal(T const& a, T const& b) {
+  using bytes = unsigned char[sizeof(T)];
+  return __builtin_memcmp(
+         __builtin_bit_cast(bytes,a),
+         __builtin_bit_cast(bytes,b),
+         sizeof(T)) == 0;
+}
+static_assert( equal(b, B{}), "");
+static_assert( equal(d, D{}), "");
+static_assert( equal(d, D{a,b}), "");