On 4/13/20 8:43 PM, Jason Merrill wrote:
On 4/12/20 5:49 PM, Martin Sebor wrote:
On 4/10/20 8:52 AM, Jason Merrill wrote:
On 4/9/20 4:23 PM, Martin Sebor wrote:
On 4/9/20 1:32 PM, Jason Merrill wrote:
On 4/9/20 3:24 PM, Martin Sebor wrote:
On 4/9/20 1:03 PM, Jason Merrill wrote:
On 4/8/20 1:23 PM, Martin Sebor wrote:
On 4/7/20 3:36 PM, Marek Polacek wrote:
On Tue, Apr 07, 2020 at 02:46:52PM -0600, Martin Sebor wrote:
On 4/7/20 1:50 PM, Marek Polacek wrote:
On Tue, Apr 07, 2020 at 12:50:48PM -0600, Martin Sebor via
Gcc-patches wrote:
Among the numerous regressions introduced by the change
committed
to GCC 9 to allow string literals as template arguments is a
failure
to recognize the C++ nullptr and GCC's __null constants as
pointers.
For one, I didn't realize that nullptr, being a null pointer
constant,
doesn't have a pointer type, and two, I didn't think of
__null (which
is a special integer constant that NULL sometimes expands to).
The attached patch adjusts the special handling of trailing
zero
initializers in reshape_init_array_1 to recognize both kinds of
constants and avoid treating them as zeros of the array integer
element type. This restores the expected diagnostics when
either
constant is used in the initializer list.
Martin
PR c++/94510 - nullptr_t implicitly cast to zero twice in
std::array
gcc/cp/ChangeLog:
PR c++/94510
* decl.c (reshape_init_array_1): Exclude mismatches with
all kinds
of pointers.
gcc/testsuite/ChangeLog:
PR c++/94510
* g++.dg/init/array57.C: New test.
* g++.dg/init/array58.C: New test.
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index a127734af69..692c8ed73f4 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -6041,9 +6041,14 @@ reshape_init_array_1 (tree elt_type,
tree max_index, reshape_iter *d,
TREE_CONSTANT (new_init) = false;
/* Pointers initialized to strings must be treated
as non-zero
- even if the string is empty. */
+ even if the string is empty. Handle all kinds of
pointers,
+ including std::nullptr and GCC's __nullptr, neither of
which
+ has a pointer type. */
tree init_type = TREE_TYPE (elt_init);
- if (POINTER_TYPE_P (elt_type) != POINTER_TYPE_P
(init_type)
+ bool init_is_ptr = (POINTER_TYPE_P (init_type)
+ || NULLPTR_TYPE_P (init_type)
+ || null_node_p (elt_init));
+ if (POINTER_TYPE_P (elt_type) != init_is_ptr
|| !type_initializer_zero_p (elt_type, elt_init))
last_nonzero = index;
It looks like this still won't handle e.g. pointers to member
functions,
e.g.
struct S { };
int arr[3] = { (void (S::*) ()) 0, 0, 0 };
would still be accepted. You could use TYPE_PTR_OR_PTRMEM_P
instead of
POINTER_TYPE_P to catch this case.
Good catch! That doesn't fail because unlike null data member
pointers
which are represented as -1, member function pointers are
represented
as a zero.
I had looked for an API that would answer the question: "is this
expression a pointer?" without having to think of all the
different
kinds of them but all I could find was null_node_p(). Is this
a rare,
isolated case that having an API like that wouldn't be worth
having
or should I add one like in the attached update?
Martin
PR c++/94510 - nullptr_t implicitly cast to zero twice in
std::array
gcc/cp/ChangeLog:
PR c++/94510
* decl.c (reshape_init_array_1): Exclude mismatches with
all kinds
of pointers.
* gcc/cp/cp-tree.h (null_pointer_constant_p): New function.
(Drop the gcc/cp/.)
+/* Returns true if EXPR is a null pointer constant of any
type. */
+
+inline bool
+null_pointer_constant_p (tree expr)
+{
+ STRIP_ANY_LOCATION_WRAPPER (expr);
+ if (expr == null_node)
+ return true;
+ tree type = TREE_TYPE (expr);
+ if (NULLPTR_TYPE_P (type))
+ return true;
+ if (POINTER_TYPE_P (type))
+ return integer_zerop (expr);
+ return null_member_pointer_value_p (expr);
+}
+
We already have a null_ptr_cst_p so it would be sort of
confusing to have
this as well. But are you really interested in whether it's a
null pointer,
not just a pointer?
The goal of the code is to detect a mismatch in "pointerness"
between
an initializer expression and the type of the initialized
element, so
it needs to know if the expression is a pointer (non-nulls pointers
are detected in type_initializer_zero_p). That means testing a
number
of IMO unintuitive conditions:
TYPE_PTR_OR_PTRMEM_P (TREE_TYPE (expr))
|| NULLPTR_TYPE_P (TREE_TYPE (expr))
|| null_node_p (expr)
I don't know if this type of a query is common in the C++ FE but
unless
this is an isolated use case then besides fixing the bug I
thought it
would be nice to make it easier to get the test above right, or
at least
come close to it.
Since null_pointer_constant_p already exists (but isn't suitable
here
because it returns true for plain literal zeros)
Why is that unsuitable? A literal zero is a perfectly good
zero-initializer for a pointer.
Right, that's why it's not suitable here. Because a literal zero
is also not a pointer.
The question the code asks is: "is the initializer expression
a pointer (of any kind)?"
Why is that a question we want to ask? What we need here is to
know whether the initializer expression is equivalent to implicit
zero-initialization. For initializing a pointer, a literal 0 is
equivalent, so we don't want to update last_nonzero.
Yes, but that's not the bug we're fixing. The problem occurs with
an integer array and a pointer initializer:
int a[2] = { nullptr, 0 };
Aha, you're fixing a different bug than the one I was seeing.
What is that one? (I'm not aware of any others in this area.)
and with elt_type = TREE_TYPE (a) and init_type TREE_TYPE (nullptr)
the test
POINTER_TYPE_P (elt_type) != POINTER_TYPE_P (init_type)
evaluates to false because neither type is a pointer type and
type_initializer_zero_p (elt_type, elt_init)
returns true because nullptr is zero, and so last_nonzero doesn't
get set, the element gets trimmed, and the invalid initialization
of int with nullptr isn't diagnosed.
But I'm not sure if you're questioning the current code, the simple
fix quoted above, or my assertion that null_pointer_constant_p would
not be a suitable function to call to tell if an initializer is
nullptr vs plain zero.
Also, why is the pointer check here rather than part of the
POINTER_TYPE_P handling in type_initializer_zero_p?
type_initializer_zero_p is implemented in terms of initializer_zerop
with the only difference that empty strings are considered to be zero
only for char arrays and not char pointers.
Yeah, but that's the fundamental problem: We're assuming that any
zero is suitable for initializing any type except for a few
exceptions, and adding more exceptions when we find a new testcase
that breaks.
Handling this in process_init_constructor_array avoids all these
problems by looking at the initializers after they've been converted
to the desired type, at which point it's much clearer whether they
are zero or not; then we don't need type_initializer_zero_p because
the initializer already has the proper type and for zero_init_p types
we can just use initializer_zero_p.
I've already expressed my concerns with that change but if you are
comfortable with it I won't insist on waiting until GCC 11. Your last
request for that patch was to rework the second loop to avoid changing
the counter of the previous loop. The attached update does that.
I also added another C++ 2a test to exercise a few more cases with
pointers to members. With it I ran into what looks like an unrelated
bug in this area. I opened PR 94568 for it, CC'd you, and xfailed
the problem case in the new test.
We do probably want some function that tests whether a particular
initializer is equivalent to zero-initialization, which is either
initializer_zero_p for zero_init_p types, !expr for pointers to
members, and recursing for aggregates. Maybe cp_initializer_zero_p
or zero_init_expr_p?
It could be changed to return false for incompatible initializers
like pointers (or even __null) for non-pointer types, even if they
are zero, but that's not what it's designed to do.
But that's exactly what we did for 90938. Now you're proposing
another small exception, only putting it in the caller instead. I
think we'll keep running into these problems until we fix the design
issue.
Somehow that felt different. But I don't have a problem with moving
the pointer check there as well. It shouldn't be too much more
intrusive than the original patch for this bug if you decide to
go with it for now.
It would also be possible to improve things by doing the conversion
in type_initializer_zero_p before considering its zeroness, but that
would again be duplicating work that we're already doing elsewhere.
I agree that it's not worth the trouble given the long-term fix is
in process_init_constructor_array.
Attached is the updated patch with the process_init_constructor_array
changes, retested on x86_64-linux.
+ if (!trunc_zero || !type_initializer_zero_p (eltype, ce->value))
+ last_nonzero = i;
I think we can remove type_initializer_zero_p as well, and use
initializer_zerop here.
+ if (last_nonzero < i - 1)
+ {
+ vec_safe_truncate (v, last_nonzero + 1);
This looks like you will never truncate to length 0, which seems like a
problem with last_nonzero being both unsigned and an index; perhaps it
should be something like num_to_keep?
This whole block appears to serve no real purpose. It trims trailing
zeros only from arithmetic types, but the trimming only matters for
pointers to members and that's done later. I've removed it.
+ len = i = vec_safe_length (v);
+ }
Nitpick: It seems you don't need to update len or i since you're about
to return.
- else if (TREE_CODE (next) == CONSTRUCTOR
- && CONSTRUCTOR_PLACEHOLDER_BOUNDARY (next))
- {
- /* As above. */
- CONSTRUCTOR_PLACEHOLDER_BOUNDARY (next) = 0;
- CONSTRUCTOR_PLACEHOLDER_BOUNDARY (init) = 1;
- }
This is from the recent fix for 90996, we want to keep it.
Whoops. But no test failed with this change, not even pr90996.C (with
make check-c++-all). I'm not sure how to write one that does fail.
/* Set to the index of the last initializer element whose value
(either as a single expression or as a repeating range) to append
to the CONSTRUCTOR. */
unsigned HOST_WIDE_INT last_to_append = i;
OK, I was wrong, let's go back to modifying i instead of introducing
this variable.
if (next)
{
picflags |= picflag_from_initializer (next);
if (initializer_constant_valid_p (next, TREE_TYPE (next))
== null_pointer_node)
{
/* If the last distinct explicit initializer value is
the same as the implicit initializer NEXT built
above, include the former in the range built below. */
if (i && next == (*v)[last_distinct].value)
last_to_append = last_distinct;
break;
}
CONSTRUCTOR_APPEND_ELT (v, size_int (i), next);
last_distinct = i;
}
else
{
/* Don't bother checking all the other elements and avoid
appending to the initializer list below. */
last_distinct = i;
last_to_append = i + 1;
break;
}
if (last_distinct < last_to_append - 1)
Could this be
if (len > i+1 && next)
instead, and never look at last_distinct again? If next is the same as
the last_distinct value, we will have adjusted i to be last_distinct;
otherwise, we still want to start the range with i.
It's not the same and breaks tests. But even if it could be, it makes
no difference in readability.
That said, I do find the second loop with the subsequent test awkward
to work with. I've adjusted it a little to make it easier for me to
follow (but YMMV). I also added more tests. Together this let me
uncover a subtle bug in the code (not setting the right null pointer
range after non-empty non-null initializers).
Attached is another revision of this patch, retested on x85_64-linux.
Martin
PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
gcc/cp/ChangeLog:
PR c++/94510
* decl.c (reshape_init_array_1): Avoid stripping redundant trailing
zero initializers here...
* typeck2.c (process_init_constructor_array): ...and instead strip
them here but only when they involve pointers to members. Extend
the range of same trailing implicit initializers to also include
preceding explicit initializers.
gcc/testsuite/ChangeLog:
PR c++/94510
* g++.dg/init/array58.C: New test.
* g++.dg/init/array59.C: New test.
* g++.dg/cpp2a/nontype-class34.C: New test.
* g++.dg/cpp2a/nontype-class35.C: New test.
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index a6a1340e631..005cc654d4e 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -6025,9 +6025,6 @@ reshape_init_array_1 (tree elt_type, tree max_index, reshape_iter *d,
max_index_cst = tree_to_uhwi (fold_convert (size_type_node, max_index));
}
- /* Set to the index of the last element with a non-zero initializer.
- Zero initializers for elements past this one can be dropped. */
- unsigned HOST_WIDE_INT last_nonzero = -1;
/* Loop until there are no more initializers. */
for (index = 0;
d->cur != d->end && (!sized_array_p || index <= max_index_cst);
@@ -6054,50 +6051,11 @@ reshape_init_array_1 (tree elt_type, tree max_index, reshape_iter *d,
if (!TREE_CONSTANT (elt_init))
TREE_CONSTANT (new_init) = false;
- /* Pointers initialized to strings must be treated as non-zero
- even if the string is empty. */
- tree init_type = TREE_TYPE (elt_init);
- if (POINTER_TYPE_P (elt_type) != POINTER_TYPE_P (init_type)
- || !type_initializer_zero_p (elt_type, elt_init))
- last_nonzero = index;
-
/* This can happen with an invalid initializer (c++/54501). */
if (d->cur == old_cur && !sized_array_p)
break;
}
- if (sized_array_p && trivial_type_p (elt_type))
- {
- /* Strip trailing zero-initializers from an array of a trivial
- type of known size. They are redundant and get in the way
- of telling them apart from those with implicit zero value. */
- unsigned HOST_WIDE_INT nelts = CONSTRUCTOR_NELTS (new_init);
- if (last_nonzero > nelts)
- nelts = 0;
- else if (last_nonzero < nelts - 1)
- nelts = last_nonzero + 1;
-
- /* Sharing a stripped constructor can get in the way of
- overload resolution. E.g., initializing a class from
- {{0}} might be invalid while initializing the same class
- from {{}} might be valid. */
- if (reuse && nelts < CONSTRUCTOR_NELTS (new_init))
- {
- vec<constructor_elt, va_gc> *v;
- vec_alloc (v, nelts);
- for (unsigned int i = 0; i < nelts; i++)
- {
- constructor_elt elt = *CONSTRUCTOR_ELT (new_init, i);
- if (TREE_CODE (elt.value) == CONSTRUCTOR)
- elt.value = unshare_constructor (elt.value);
- v->quick_push (elt);
- }
- new_init = build_constructor (TREE_TYPE (new_init), v);
- }
- else
- vec_safe_truncate (CONSTRUCTOR_ELTS (new_init), nelts);
- }
-
return new_init;
}
diff --git a/gcc/cp/typeck2.c b/gcc/cp/typeck2.c
index 56fd9bafa7e..374c3989480 100644
--- a/gcc/cp/typeck2.c
+++ b/gcc/cp/typeck2.c
@@ -1477,6 +1477,31 @@ process_init_constructor_array (tree type, tree init, int nested, int flags,
return PICFLAG_ERRONEOUS;
}
+ /* Process any explicit initializers, replacing series of same
+ trailing initializers are appended as just one, using a range
+ of indices. For example:
+ struct A { int i; }; typedef int A::* P;
+ P[7] = { &A::i, &A::i, 0, 0 };
+ is transformed into a CONSTRUCTOR with the three elements:
+ { 0, 0, [2..6] = -1 }
+ (because the "address" or offset of A::i is zero and because a null
+ data member pointer is represented as all ones). This is done so
+ that equivalent non-type template arguments that literals of such
+ types are treated as identical regardless of the form their
+ (equivalent) initializers take. */
+ const tree eltype = TREE_TYPE (type);
+ /* Set if ELTYPE requires a ctor call. */
+ const bool build_ctor = type_build_ctor_call (eltype);
+ /* Set if default-initializing ELTYPE zeroes out its bits. */
+ const bool zero_init = zero_init_p (eltype);
+ /* Set if any trailing zeros are redundant. */
+ const bool bounded_zero_init = !unbounded && !build_ctor && zero_init;
+
+ /* Set to the zero-based index of the last element with a distinct
+ initializer value. Subsequent elements (if any) have the same
+ value that's different from that of LAST_DISTINCT. */
+ unsigned HOST_WIDE_INT last_distinct = 0;
+
FOR_EACH_VEC_SAFE_ELT (v, i, ce)
{
if (!ce->index)
@@ -1506,64 +1531,97 @@ process_init_constructor_array (tree type, tree init, int nested, int flags,
strip_array_types (TREE_TYPE (ce->value)))));
picflags |= picflag_from_initializer (ce->value);
+
+ if (ce->value != (*v)[last_distinct].value)
+ last_distinct = i;
}
- /* No more initializers. If the array is unbounded, we are done. Otherwise,
- we must add initializers ourselves. */
- if (!unbounded)
- for (; i < len; ++i)
- {
- tree next;
+ /* No more initializers. If the array is unbounded or if trailing
+ zero-initialized elements can be elided, we are done. */
+ if (unbounded || bounded_zero_init)
+ {
+ CONSTRUCTOR_ELTS (init) = v;
+ return picflags;
+ }
- if (type_build_ctor_call (TREE_TYPE (type)))
- {
- /* If this type needs constructors run for default-initialization,
- we can't rely on the back end to do it for us, so make the
- initialization explicit by list-initializing from T{}. */
- next = build_constructor (init_list_type_node, NULL);
- next = massage_init_elt (TREE_TYPE (type), next, nested, flags,
- complain);
- if (initializer_zerop (next))
- /* The default zero-initialization is fine for us; don't
- add anything to the CONSTRUCTOR. */
- next = NULL_TREE;
- else if (TREE_CODE (next) == CONSTRUCTOR
- && CONSTRUCTOR_PLACEHOLDER_BOUNDARY (next))
- {
- /* As above. */
- CONSTRUCTOR_PLACEHOLDER_BOUNDARY (next) = 0;
- CONSTRUCTOR_PLACEHOLDER_BOUNDARY (init) = 1;
- }
- }
- else if (!zero_init_p (TREE_TYPE (type)))
- next = build_zero_init (TREE_TYPE (type),
- /*nelts=*/NULL_TREE,
- /*static_storage_p=*/false);
- else
- /* The default zero-initialization is fine for us; don't
- add anything to the CONSTRUCTOR. */
- next = NULL_TREE;
+ /* Set to the next initializer value to append. */
+ tree next = NULL_TREE;
+
+ for (; i < len; ++i)
+ {
+ if (build_ctor)
+ {
+ /* If this type needs constructors run for default-initialization,
+ we can't rely on the back end to do it for us, so make the
+ initialization explicit by list-initializing from T{}. */
+ next = build_constructor (init_list_type_node, NULL);
+ next = massage_init_elt (eltype, next, nested, flags, complain);
+ if (initializer_zerop (next))
+ {
+ /* The default zero-initialization is fine for us; don't
+ add anything to the CONSTRUCTOR. */
+ CONSTRUCTOR_ELTS (init) = v;
+ return picflags;
+ }
+
+ if (TREE_CODE (next) == CONSTRUCTOR
+ && CONSTRUCTOR_PLACEHOLDER_BOUNDARY (next))
+ {
+ /* As above. */
+ CONSTRUCTOR_PLACEHOLDER_BOUNDARY (next) = 0;
+ CONSTRUCTOR_PLACEHOLDER_BOUNDARY (init) = 1;
+ }
+ }
+ else if (zero_init)
+ {
+ /* Don't bother checking all the other elements and avoid
+ appending to the initializer list below. */
+ CONSTRUCTOR_ELTS (init) = v;
+ return picflags;
+ }
+ else
+ next = build_zero_init (eltype,
+ /*nelts=*/NULL_TREE,
+ /*static_storage_p=*/false);
+
+ picflags |= picflag_from_initializer (next);
+ if (initializer_constant_valid_p (next, TREE_TYPE (next))
+ == null_pointer_node)
+ {
+ /* If the last distinct explicit initializer value is the same
+ compile-time constant as the implicit initializer NEXT built
+ above include the former in the range built below. */
+ if (i && next == (*v)[last_distinct].value)
+ i = len;
- if (next)
- {
- picflags |= picflag_from_initializer (next);
- if (len > i+1
- && (initializer_constant_valid_p (next, TREE_TYPE (next))
- == null_pointer_node))
- {
- tree range = build2 (RANGE_EXPR, size_type_node,
- build_int_cst (size_type_node, i),
- build_int_cst (size_type_node, len - 1));
- CONSTRUCTOR_APPEND_ELT (v, range, next);
- break;
- }
- else
- CONSTRUCTOR_APPEND_ELT (v, size_int (i), next);
- }
- else
- /* Don't bother checking all the other elements. */
break;
- }
+ }
+
+ CONSTRUCTOR_APPEND_ELT (v, size_int (i), next);
+ last_distinct = i;
+ }
+
+ if (last_distinct < i - 1)
+ {
+ /* Insert a range [LAST_DISTINCT, LEN) initializing trailing
+ elements to the same constant value built above and stored
+ in NEXT. */
+ tree range = build2 (RANGE_EXPR, size_type_node,
+ build_int_cst (size_type_node, last_distinct),
+ build_int_cst (size_type_node, len - 1));
+ unsigned HOST_WIDE_INT nelts = last_distinct + 1;
+ if (vec_safe_length (v) < nelts)
+ /* Append the range if there is no LAST_DISTINCT initializer. */
+ CONSTRUCTOR_APPEND_ELT (v, range, next);
+ else
+ {
+ /* Replace the LAST_DISTINCT initializer with NEXT. */
+ vec_safe_truncate (v, nelts);
+ (*v)[last_distinct].index = range;
+ }
+ }
+ else if (i < len && next)
+ CONSTRUCTOR_APPEND_ELT (v, size_int (i), next);
CONSTRUCTOR_ELTS (init) = v;
return picflags;
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class34.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class34.C
new file mode 100644
index 00000000000..0baec5163b3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class34.C
@@ -0,0 +1,76 @@
+/* PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
+ { dg-do compile { target c++2a } }
+ { dg-options "-Wall" } */
+
+struct A { int i; int f (); };
+typedef int A::*MemPtr;
+typedef int (A::*MemFuncPtr)();
+
+struct B { MemPtr a[3]; MemFuncPtr b[3]; };
+
+static const constexpr MemPtr mp0 = { 0 };
+static const constexpr MemPtr mpn = { nullptr };
+static const constexpr MemPtr mp_ = { };
+static const constexpr MemPtr mpi = { &A::i };
+
+template <B> struct X { };
+
+typedef X<B{ }> XB;
+typedef X<B{ 0 }> XB;
+typedef X<B{{ 0 }}> XB;
+typedef X<B{{ MemPtr{ }}}> XB;
+typedef X<B{{ MemPtr{ 0 }}}> XB;
+typedef X<B{{ MemPtr () }}> XB;
+typedef X<B{{ MemPtr{ nullptr }}}> XB;
+typedef X<B{{ mp_ }}> XB; // { dg-bogus "conflicting declaration" "pr94568" { xfail *-*-* } }
+typedef X<B{{ mpn }}> XB;
+typedef X<B{{ mp0 }}> XB;
+
+typedef X<B{ mpi }> XBp;
+typedef X<B{ mpi, 0 }> XBp;
+typedef X<B{{ mpi, 0 }}> XBp;
+typedef X<B{{ mpi, MemPtr{ }}}> XBp;
+typedef X<B{{ mpi, MemPtr{ 0 }}}> XBp;
+typedef X<B{{ mpi, MemPtr () }}> XBp;
+typedef X<B{{ mpi, MemPtr{ nullptr }}}> XBp;
+typedef X<B{{ mpi, mp_ }}> XBp; // { dg-bogus "conflicting declaration" "pr94568" { xfail *-*-* } }
+typedef X<B{{ mpi, mpn }}> XBp;
+typedef X<B{{ mpi, mp0 }}> XBp;
+
+typedef X<B{ mpi, mpi }> XBpp;
+typedef X<B{ mpi, mpi, 0 }> XBpp;
+typedef X<B{{ mpi, mpi, 0 }}> XBpp;
+typedef X<B{{ mpi, mpi, MemPtr{ }}}> XBpp;
+typedef X<B{{ mpi, mpi, MemPtr{ 0 }}}> XBpp;
+typedef X<B{{ mpi, mpi, MemPtr () }}> XBpp;
+typedef X<B{{ mpi, mpi, MemPtr{ nullptr }}}> XBpp;
+typedef X<B{{ mpi, mpi, mp_ }}> XBpp; // { dg-bogus "conflicting declaration" "pr94568" { xfail *-*-* } }
+typedef X<B{{ mpi, mpi, mpn }}> XBpp;
+typedef X<B{{ mpi, mpi, mp0 }}> XBpp;
+
+typedef X<B{ 0, mpi }> XB0p;
+typedef X<B{ nullptr, mpi, 0 }> XB0p;
+typedef X<B{ mp0, mpi, 0 }> XB0p;
+
+typedef X<B{ 0, 0, mpi }> XB00p;
+typedef X<B{ 0, nullptr, mpi }> XB00p;
+typedef X<B{ nullptr, 0, mpi }> XB00p;
+typedef X<B{ nullptr, nullptr, mpi }> XB00p;
+typedef X<B{ MemPtr{ }, MemPtr{ }, mpi }> XB00p;
+typedef X<B{ mp0, MemPtr{ }, mpi }> XB00p;
+typedef X<B{ mpn, mpn, mpi }> XB00p;
+typedef X<B{ mpn, mp_, mpi }> XB00p; // { dg-bogus "conflicting declaration" "pr94568" { xfail *-*-* } }
+
+static const constexpr MemFuncPtr mfp0 = { 0 };
+static const constexpr MemFuncPtr mfpn = { nullptr };
+static const constexpr MemFuncPtr mfp_ = { };
+
+typedef X<B{{ }, { }}> XB;
+typedef X<B{{ }, { 0 }}> XB;
+typedef X<B{{ }, { MemFuncPtr{ }}}> XB;
+typedef X<B{{ }, { MemFuncPtr{ 0 }}}> XB;
+typedef X<B{{ }, { MemFuncPtr () }}> XB;
+typedef X<B{{ }, { MemFuncPtr{ nullptr }}}> XB;
+typedef X<B{{ }, { mfp_ }}> XB;
+typedef X<B{{ }, { mfpn }}> XB;
+typedef X<B{{ }, { mfp0 }}> XB;
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class35.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class35.C
new file mode 100644
index 00000000000..5649fa2e6dc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class35.C
@@ -0,0 +1,80 @@
+/* PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
+ { dg-do compile { target c++2a } }
+ { dg-options "-Wall" } */
+
+struct A { char a[4]; };
+template <A> struct B { };
+
+constexpr const char c0{ };
+constexpr const char c1{ 1 };
+
+typedef B<A{ }> BA;
+typedef B<A{ { } }> BA;
+typedef B<A{ { 0 } }> BA;
+typedef B<A{ { c0 } }> BA;
+typedef B<A{ { 0, 0 } }> BA;
+typedef B<A{ { 0, 0, 0 } }> BA;
+typedef B<A{ { 0, 0, 0, 0 } }> BA;
+typedef B<A{ { c0, c0, c0 } }> BA;
+typedef B<A{ { c0, c0, c0, c0 } }> BA;
+typedef B<A{ "" }> BA;
+typedef B<A{ "\0" }> BA;
+typedef B<A{ "\0\0" }> BA;
+typedef B<A{ "\0\0\0" }> BA;
+
+typedef B<A{ 1 }> BA1;
+typedef B<A{ { 1 } }> BA1;
+typedef B<A{ { 1, 0 } }> BA1;
+typedef B<A{ { 1, 0, 0 } }> BA1;
+typedef B<A{ { 1, 0, 0, 0 } }> BA1;
+typedef B<A{ { c1 } }> BA1;
+typedef B<A{ { c1, c0 } }> BA1;
+typedef B<A{ { c1, c0, c0 } }> BA1;
+typedef B<A{ { c1, c0, c0, c0 } }> BA1;
+typedef B<A{ "\1" }> BA1;
+typedef B<A{ "\1\0" }> BA1;
+typedef B<A{ "\1\0\0" }> BA1;
+
+typedef B<A{ 0, 1 }> BA01;
+typedef B<A{ { 0, 1 } }> BA01;
+typedef B<A{ { 0, 1, 0 } }> BA01;
+typedef B<A{ { 0, 1, 0, 0 } }> BA01;
+typedef B<A{ { c0, c1 } }> BA01;
+typedef B<A{ { c0, c1, c0 } }> BA01;
+typedef B<A{ { c0, c1, c0, c0 } }> BA01;
+typedef B<A{ "\0\1" }> BA01;
+typedef B<A{ "\0\1\0" }> BA01;
+
+
+struct C { int a[4]; };
+template <C> struct D { };
+
+constexpr const int i0{ };
+
+typedef D<C{ }> DC;
+typedef D<C{ { } }> DC;
+typedef D<C{ { 0 } }> DC;
+typedef D<C{ { 0, 0 } }> DC;
+typedef D<C{ { 0, 0, 0 } }> DC;
+typedef D<C{ { 0, 0, 0, 0 } }> DC;
+typedef D<C{ { i0 } }> DC;
+typedef D<C{ { i0, i0 } }> DC;
+typedef D<C{ { i0, i0, i0 } }> DC;
+typedef D<C{ { i0, i0, i0, i0 } }> DC;
+
+
+constexpr const int i1{ 1 };
+
+typedef D<C{ 1 }> DC1;
+typedef D<C{ { 1 } }> DC1;
+typedef D<C{ { 1, 0 } }> DC1;
+typedef D<C{ { 1, 0, 0 } }> DC1;
+typedef D<C{ { 1, 0, 0, 0 } }> DC1;
+typedef D<C{ { i1, i0, i0, i0 } }> DC1;
+
+typedef D<C{ 0, 1 }> DC01;
+typedef D<C{ { 0, 1 } }> DC01;
+typedef D<C{ { 0, 1, 0 } }> DC01;
+typedef D<C{ { 0, 1, 0, 0 } }> DC01;
+typedef D<C{ { 0, i1, 0, 0 } }> DC01;
+typedef D<C{ { i0, i1, i0, i0 } }> DC01; // { dg-bogus "conflicting declaration" "pr94567" { xfail *-*-* } }
diff --git a/gcc/testsuite/g++.dg/init/array58.C b/gcc/testsuite/g++.dg/init/array58.C
new file mode 100644
index 00000000000..70e86445c07
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array58.C
@@ -0,0 +1,26 @@
+/* PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
+ { dg-do compile } */
+
+int ia1[2] = { (void*)0 }; // { dg-error "invalid conversion from 'void\\\*'" }
+int ia2[2] = { (void*)0, 0 }; // { dg-error "invalid conversion from 'void\\\*'" }
+int ia3[] = { (void*)0, 0 }; // { dg-error "invalid conversion from 'void\\\*'" }
+
+int ia4[2] = { __null }; // { dg-warning "\\\[-Wconversion-null" }
+int ia5[2] = { __null, 0 }; // { dg-warning "\\\[-Wconversion-null" }
+int ia6[] = { __null, 0 }; // { dg-warning "\\\[-Wconversion-null" }
+
+
+const char ca1[2] = { (char*)0, 0 }; // { dg-error "invalid conversion from 'char\\\*'" }
+
+const char ca2[2] = { __null, 0 }; // { dg-warning "\\\[-Wconversion-null" }
+
+
+typedef void Func ();
+const char ca6[2] = { (Func*)0, 0 }; // { dg-error "invalid conversion from 'void \\\(\\\*\\\)\\\(\\\)' to 'char'" }
+
+struct S;
+typedef int S::*MemPtr;
+typedef int (S::*MemFuncPtr)();
+
+const char ca4[2] = { (MemPtr)0, 0 }; // { dg-error "cannot convert 'MemPtr' " }
+const char ca5[2] = { (MemFuncPtr)0, 0 }; // { dg-error "cannot convert 'int \\\(S::\\\*\\\)\\\(\\\)' " }
diff --git a/gcc/testsuite/g++.dg/init/array59.C b/gcc/testsuite/g++.dg/init/array59.C
new file mode 100644
index 00000000000..e8680de9456
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array59.C
@@ -0,0 +1,42 @@
+/* PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
+ { dg-do compile { target c++11 } } */
+
+namespace std {
+typedef __typeof__ (nullptr) nullptr_t;
+}
+
+int ia1[2] = { nullptr }; // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+int ia2[2] = { nullptr, 0 }; // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+int ia3[] = { nullptr, 0 }; // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+
+int ia4[2] = { (std::nullptr_t)0 }; // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+int ia5[2] = { (std::nullptr_t)0, 0 }; // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+int ia6[] = { (std::nullptr_t)0, 0 }; // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+
+
+const char ca1[2] = { nullptr, 0 }; // { dg-error "cannot convert 'std::nullptr_t' to 'const char'" }
+
+const char ca2[2] = { (char*)nullptr, 0 };// { dg-error "invalid conversion from 'char\\\*' to 'char'" }
+
+const char ca3[2] = { std::nullptr_t () };// { dg-error "cannot convert 'std::nullptr_t'" }
+
+/* Verify that arrays of member pointers can be initialized by a literal
+ zero as well as nullptr. */
+
+struct S { };
+typedef int S::*MemPtr;
+typedef int (S::*MemFuncPtr)();
+
+MemPtr mp1[3] = { 0, nullptr, (MemPtr)0 };
+MemPtr mp2[3] = { 0, std::nullptr_t (), MemPtr () };
+
+MemPtr mp3[3] = { 0, (void*)0 }; // { dg-error "cannot convert 'void\\\*' to 'MemPtr' " }
+MemPtr mp4[3] = { 0, (S*)0 }; // { dg-error "cannot convert 'S\\\*' to 'MemPtr' " }
+MemPtr mp5[3] = { 0, S () }; // { dg-error "cannot convert 'S' to 'MemPtr' " }
+
+MemFuncPtr mfp1[3] = { 0, nullptr, (MemFuncPtr)0 };
+MemFuncPtr mfp2[3] = { 0, std::nullptr_t (), MemFuncPtr () };
+
+MemFuncPtr mfp3[3] = { 0, (void*)0 }; // { dg-error "cannot convert 'void\\\*' to 'MemFuncPtr' " }
+MemFuncPtr mfp4[3] = { 0, (S*)0 }; // { dg-error "cannot convert 'S\\\*' to 'MemFuncPtr' " }
+MemFuncPtr mfp5[3] = { 0, S () }; // { dg-error "cannot convert 'S' to 'MemFuncPtr' " }