diff mbox series

c++: NTTP object wrapper substitution fixes [PR103346, ...]

Message ID 20221206175702.987794-1-ppalka@redhat.com
State New
Headers show
Series c++: NTTP object wrapper substitution fixes [PR103346, ...] | expand

Commit Message

Patrick Palka Dec. 6, 2022, 5:57 p.m. UTC
This patch fixes some issues with substitution into a C++20 template
parameter object wrapper:

* The first testcase demonstrates a situation where the same_type_p
  assert in relevant case of tsubst_copy doesn't hold, because (partial)
  substitution of {int,} into the VIEW_CONVERT_EXPR wrapper yields
  A<int> but substitution into the underlying TEMPLATE_PARM_INDEX is a
  nop and yields A<T> due to tsubst's level == 1 early exit test.  So
  this patch just gets rid of the assert; the type mismatch doesn't
  seem to be a problem in practice since the coercion is from one
  dependent type to another.

* In the second testcase, dependent substitution into the underlying
  TEMPLATE_PARM_INDEX yields a CALL_EXPR with empty TREE_TYPE, which
  tsubst_copy doesn't expect.  This patch fixes this by handling empty
  TREE_TYPE the same way as a non-const TREE_TYPE.  Moreover, after
  this substitution we're left with a VIEW_CONVERT_EXPR wrapping a
  CALL_EXPR instead of a TEMPLATE_PARM_INDEX, which during the subsequent
  non-dependent substitution tsubst_copy doesn't expect either.  So
  this patch also relaxes the tsubst_copy case to accept such
  VIEW_CONVERT_EXPR too.

* In the third testcase, we end up never resolving the call to
  f.modify() since tsubst_copy doesn't do overload resolution.
  This patch fixes this by moving the handling of these
  VIEW_CONVERT_EXPR wrappers from tsubst_copy to tsubst_copy_and_build.
  And it turns out (at least according to our testsuite) that
  tsubst_copy doesn't directly need to handle the other kinds of
  NON_LVALUE_EXPR and VIEW_CONVERT_EXPR, so this patch also gets rid
  of the location_wrapper_p handling from tsubst_copy and moves the
  REF_PARENTHESIZED_P handling to tsubst_copy_and_build.

After this patch, VIEW_CONVERT_EXPR substitution is ultimately just
moved from tsubst_copy to tsubst_copy_and_build and made more
permissive.

Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
trunk?

	PR c++/103346
	PR c++/104278
	PR c++/102553

gcc/cp/ChangeLog:

	* pt.cc (tsubst_copy) <case NON_LVALUE_EXPR, VIEW_CONVERT_EXPR>:
	Remove same_type_p assert.  Accept non-TEMPLATE_PARM_INDEX inner
	operand.  Handle empty TREE_TYPE on substituted inner operand.
	Move all of this handling to ...
	(tsubst_copy_and_build): ... here and simplify accordingly.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/nontype-class52a.C: New test.
	* g++.dg/cpp2a/nontype-class53.C: New test.
	* g++.dg/cpp2a/nontype-class54.C: New test.
	* g++.dg/cpp2a/nontype-class55.C: New test.
---
 gcc/cp/pt.cc                                  | 100 +++++++-----------
 gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C |  15 +++
 gcc/testsuite/g++.dg/cpp2a/nontype-class53.C  |  25 +++++
 gcc/testsuite/g++.dg/cpp2a/nontype-class54.C  |  23 ++++
 gcc/testsuite/g++.dg/cpp2a/nontype-class55.C  |  15 +++
 5 files changed, 119 insertions(+), 59 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class55.C

Comments

Patrick Palka Dec. 6, 2022, 6:35 p.m. UTC | #1
On Tue, 6 Dec 2022, Patrick Palka wrote:

> This patch fixes some issues with substitution into a C++20 template
> parameter object wrapper:
> 
> * The first testcase demonstrates a situation where the same_type_p
>   assert in relevant case of tsubst_copy doesn't hold, because (partial)
>   substitution of {int,} into the VIEW_CONVERT_EXPR wrapper yields
>   A<int> but substitution into the underlying TEMPLATE_PARM_INDEX is a
>   nop and yields A<T> due to tsubst's level == 1 early exit test.  So
>   this patch just gets rid of the assert; the type mismatch doesn't
>   seem to be a problem in practice since the coercion is from one
>   dependent type to another.
> 
> * In the second testcase, dependent substitution into the underlying
>   TEMPLATE_PARM_INDEX yields a CALL_EXPR with empty TREE_TYPE, which
>   tsubst_copy doesn't expect.  This patch fixes this by handling empty
>   TREE_TYPE the same way as a non-const TREE_TYPE.  Moreover, after
>   this substitution we're left with a VIEW_CONVERT_EXPR wrapping a
>   CALL_EXPR instead of a TEMPLATE_PARM_INDEX, which during the subsequent
>   non-dependent substitution tsubst_copy doesn't expect either.  So
>   this patch also relaxes the tsubst_copy case to accept such
>   VIEW_CONVERT_EXPR too.
> 
> * In the third testcase, we end up never resolving the call to
>   f.modify() since tsubst_copy doesn't do overload resolution.
>   This patch fixes this by moving the handling of these
>   VIEW_CONVERT_EXPR wrappers from tsubst_copy to tsubst_copy_and_build.
>   And it turns out (at least according to our testsuite) that
>   tsubst_copy doesn't directly need to handle the other kinds of
>   NON_LVALUE_EXPR and VIEW_CONVERT_EXPR, so this patch also gets rid
>   of the location_wrapper_p handling from tsubst_copy and moves the
>   REF_PARENTHESIZED_P handling to tsubst_copy_and_build.

On second thought, getting rid of the location_wrapper_p and
REF_PARENTHESIZED_P handling from tsubst_copy is perhaps too
risky at this stage.  The following patch instead just moves
the tparm object wrapper handling from tsubst_copy to
tsubst_copy_and_build and leaves the rest of tsubst_copy alone.
Smoke tested so far, full bootstrap+regtest in progress:

-- >8--

Subject: [PATCH] c++: NTTP object wrapper substitution fixes [PR103346, ...]

This patch fixes some issues with substitution into a C++20 template
parameter object wrapper:

* The first testcase demonstrates a situation where the same_type_p
  assert in relevant case of tsubst_copy doesn't hold, because (partial)
  substitution of {int,} into the VIEW_CONVERT_EXPR wrapper yields
  A<int> but substitution into the underlying TEMPLATE_PARM_INDEX is a
  nop and yields A<T> due to tsubst's level == 1 early exit test.  So
  this patch just gets rid of the assert; the type mismatch doesn't
  seem to be a problem in practice, I suppose because the coercion is
  from one dependent type to another.

* In the second testcase, dependent substitution into the underlying
  TEMPLATE_PARM_INDEX yields a CALL_EXPR with empty TREE_TYPE, which
  tsubst_copy doesn't expect.  This patch fixes this by handling empty
  TREE_TYPE the same way as a non-const TREE_TYPE.  Moreover, after
  this substitution we're left with a VIEW_CONVERT_EXPR wrapping a
  CALL_EXPR instead of a TEMPLATE_PARM_INDEX, which during the subsequent
  non-dependent substitution tsubst_copy doesn't expect either.  So
  this patch also relaxes tsubst_copy to accept such VIEW_CONVERT_EXPR
  too.

* In the third testcase, we end up never resolving the call to
  f.modify() since tsubst_copy doesn't do overload resolution.
  This patch fixes this by moving the handling of these
  VIEW_CONVERT_EXPR wrappers from tsubst_copy to tsubst_copy_and_build.
  For good measure tsubst_copy_and_build should also handle
  REF_PARENTHESIZED_P wrappers instead of delegating to tsubst_copy.

After this patch, VIEW_CONVERT_EXPR substitution is ultimately just
moved from tsubst_copy to tsubst_copy_and_build and made more
permissive.

Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
trunk?

	PR c++/103346
	PR c++/104278
	PR c++/102553

gcc/cp/ChangeLog:

	* pt.cc (tsubst_copy) <case VIEW_CONVERT_EXPR>: In the handling
	of C++20 template parameter object wrappers: Remove same_type_p
	assert.  Accept non-TEMPLATE_PARM_INDEX inner operand.  Handle
	empty TREE_TYPE on substituted inner operand.  Move it to ...
	(tsubst_copy_and_build): ... here.  Also handle REF_PARENTHESIZED_P
	VIEW_CONVERT_EXPRs.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/nontype-class52a.C: New test.
	* g++.dg/cpp2a/nontype-class53.C: New test.
	* g++.dg/cpp2a/nontype-class54.C: New test.
	* g++.dg/cpp2a/nontype-class55.C: New test.
---
 gcc/cp/pt.cc                                  | 73 ++++++++++---------
 gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C | 15 ++++
 gcc/testsuite/g++.dg/cpp2a/nontype-class53.C  | 25 +++++++
 gcc/testsuite/g++.dg/cpp2a/nontype-class54.C  | 23 ++++++
 gcc/testsuite/g++.dg/cpp2a/nontype-class55.C  | 15 ++++
 5 files changed, 116 insertions(+), 35 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class55.C

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 2d8e4fdd4b5..0a196f069ad 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -17271,42 +17271,16 @@ tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	      return maybe_wrap_with_location (op0, EXPR_LOCATION (t));
 	    }
 	  tree op = TREE_OPERAND (t, 0);
-	  if (code == VIEW_CONVERT_EXPR
-	      && TREE_CODE (op) == TEMPLATE_PARM_INDEX)
-	    {
-	      /* Wrapper to make a C++20 template parameter object const.  */
-	      op = tsubst_copy (op, args, complain, in_decl);
-	      if (!CP_TYPE_CONST_P (TREE_TYPE (op)))
-		{
-		  /* The template argument is not const, presumably because
-		     it is still dependent, and so not the const template parm
-		     object.  */
-		  tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
-		  gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p
-				       (type, TREE_TYPE (op)));
-		  if (TREE_CODE (op) == CONSTRUCTOR
-		      || TREE_CODE (op) == IMPLICIT_CONV_EXPR)
-		    {
-		      /* Don't add a wrapper to these.  */
-		      op = copy_node (op);
-		      TREE_TYPE (op) = type;
-		    }
-		  else
-		    /* Do add a wrapper otherwise (in particular, if op is
-		       another TEMPLATE_PARM_INDEX).  */
-		    op = build1 (code, type, op);
-		}
-	      return op;
-	    }
 	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
-	  else if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
+	  if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
 	    {
 	      op = tsubst_copy (op, args, complain, in_decl);
 	      op = build1 (code, TREE_TYPE (op), op);
 	      REF_PARENTHESIZED_P (op) = true;
 	      return op;
 	    }
-	  /* We shouldn't see any other uses of these in templates.  */
+	  /* We shouldn't see any other uses of these in templates
+	     (tsubst_copy_and_build handles C++20 tparm object wrappers).  */
 	  gcc_unreachable ();
 	}
 
@@ -21569,12 +21543,41 @@ tsubst_copy_and_build (tree t,
 
     case NON_LVALUE_EXPR:
     case VIEW_CONVERT_EXPR:
-      if (location_wrapper_p (t))
-	/* We need to do this here as well as in tsubst_copy so we get the
-	   other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
-	RETURN (maybe_wrap_with_location (RECUR (TREE_OPERAND (t, 0)),
-					  EXPR_LOCATION (t)));
-      /* fallthrough.  */
+      {
+	tree op = RECUR (TREE_OPERAND (t, 0));
+	if (location_wrapper_p (t))
+	  /* We need to do this here as well as in tsubst_copy so we get the
+	     other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
+	  RETURN (maybe_wrap_with_location (op, EXPR_LOCATION (t)));
+
+	gcc_checking_assert (TREE_CODE (t) == VIEW_CONVERT_EXPR);
+	if (REF_PARENTHESIZED_P (t))
+	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
+	  RETURN (finish_parenthesized_expr (op));
+
+	/* Otherwise, we're dealing with a wrapper to make a C++20 template
+	   parameter object const.  */
+	if (TREE_TYPE (op) == NULL_TREE
+	    || !CP_TYPE_CONST_P (TREE_TYPE (op)))
+	  {
+	    /* The template argument is not const, presumably because
+	       it is still dependent, and so not the const template parm
+	       object.  */
+	    tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
+	    if (TREE_CODE (op) == CONSTRUCTOR
+		|| TREE_CODE (op) == IMPLICIT_CONV_EXPR)
+	      {
+		/* Don't add a wrapper to these.  */
+		op = copy_node (op);
+		TREE_TYPE (op) = type;
+	      }
+	    else
+	      /* Do add a wrapper otherwise (in particular, if op is
+		 another TEMPLATE_PARM_INDEX).  */
+	      op = build1 (VIEW_CONVERT_EXPR, type, op);
+	  }
+	RETURN (op);
+      }
 
     default:
       /* Handle Objective-C++ constructs, if appropriate.  */
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
new file mode 100644
index 00000000000..ae5d5df70ac
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
@@ -0,0 +1,15 @@
+// A version of nontype-class52.C where explicit template arguments are
+// given in the call to f (which during deduction need to be partially
+// substituted into the NTTP object V in f's signature).
+// { dg-do compile { target c++20 } }
+
+template<class> struct A { };
+
+template<auto> struct B { };
+
+template<class T, A<T> V> void f(B<V>);
+
+int main() {
+  constexpr A<int> a;
+  f<int>(B<a>{});
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
new file mode 100644
index 00000000000..9a6398c5f57
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
@@ -0,0 +1,25 @@
+// PR c++/103346
+// { dg-do compile { target c++20 } }
+
+struct Item {};
+
+template<class T, T... ts>
+struct Sequence { };
+
+template<Item... items>
+using ItemSequence = Sequence<Item, items...>;
+
+template<Item... items>
+constexpr auto f() {
+  constexpr auto l = [](Item item) { return item; };
+  return ItemSequence<l(items)...>{};
+}
+
+using ty0 = decltype(f<>());
+using ty0 = ItemSequence<>;
+
+using ty1 = decltype(f<{}>());
+using ty1 = ItemSequence<{}>;
+
+using ty3 = decltype(f<{}, {}, {}>());
+using ty3 = ItemSequence<{}, {}, {}>;
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
new file mode 100644
index 00000000000..8127b1f5426
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
@@ -0,0 +1,23 @@
+// PR c++/104278
+// { dg-do compile { target c++20 } }
+
+struct foo {
+  int value;
+  constexpr foo modify() const { return { value + 1 }; }
+};
+
+template<foo f, bool Enable = f.value & 1>
+struct bar {
+  static void run() { }
+};
+
+template<foo f>
+struct qux {
+  static void run() {
+    bar<f.modify()>::run();
+  }
+};
+
+int main() {
+  qux<foo{}>::run();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
new file mode 100644
index 00000000000..afcb3d64495
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
@@ -0,0 +1,15 @@
+// PR c++/102553
+// { dg-do compile { target c++20 } }
+
+struct s1{};
+template<int> inline constexpr s1 ch{};
+
+template<s1 f> struct s2{};
+template<s1 f> using alias1 = s2<f>;
+
+template<class T>
+void general(int n) {
+  alias1<ch<1>>{};
+}
+
+template void general<int>(int);
Patrick Palka Dec. 19, 2022, 4:01 p.m. UTC | #2
On Tue, 6 Dec 2022, Patrick Palka wrote:

> On Tue, 6 Dec 2022, Patrick Palka wrote:
> 
> > This patch fixes some issues with substitution into a C++20 template
> > parameter object wrapper:
> > 
> > * The first testcase demonstrates a situation where the same_type_p
> >   assert in relevant case of tsubst_copy doesn't hold, because (partial)
> >   substitution of {int,} into the VIEW_CONVERT_EXPR wrapper yields
> >   A<int> but substitution into the underlying TEMPLATE_PARM_INDEX is a
> >   nop and yields A<T> due to tsubst's level == 1 early exit test.  So
> >   this patch just gets rid of the assert; the type mismatch doesn't
> >   seem to be a problem in practice since the coercion is from one
> >   dependent type to another.
> > 
> > * In the second testcase, dependent substitution into the underlying
> >   TEMPLATE_PARM_INDEX yields a CALL_EXPR with empty TREE_TYPE, which
> >   tsubst_copy doesn't expect.  This patch fixes this by handling empty
> >   TREE_TYPE the same way as a non-const TREE_TYPE.  Moreover, after
> >   this substitution we're left with a VIEW_CONVERT_EXPR wrapping a
> >   CALL_EXPR instead of a TEMPLATE_PARM_INDEX, which during the subsequent
> >   non-dependent substitution tsubst_copy doesn't expect either.  So
> >   this patch also relaxes the tsubst_copy case to accept such
> >   VIEW_CONVERT_EXPR too.
> > 
> > * In the third testcase, we end up never resolving the call to
> >   f.modify() since tsubst_copy doesn't do overload resolution.
> >   This patch fixes this by moving the handling of these
> >   VIEW_CONVERT_EXPR wrappers from tsubst_copy to tsubst_copy_and_build.
> >   And it turns out (at least according to our testsuite) that
> >   tsubst_copy doesn't directly need to handle the other kinds of
> >   NON_LVALUE_EXPR and VIEW_CONVERT_EXPR, so this patch also gets rid
> >   of the location_wrapper_p handling from tsubst_copy and moves the
> >   REF_PARENTHESIZED_P handling to tsubst_copy_and_build.
> 
> On second thought, getting rid of the location_wrapper_p and
> REF_PARENTHESIZED_P handling from tsubst_copy is perhaps too
> risky at this stage.  The following patch instead just moves
> the tparm object wrapper handling from tsubst_copy to
> tsubst_copy_and_build and leaves the rest of tsubst_copy alone.
> Smoke tested so far, full bootstrap+regtest in progress:
> 
> -- >8--
> 
> Subject: [PATCH] c++: NTTP object wrapper substitution fixes [PR103346, ...]
> 
> This patch fixes some issues with substitution into a C++20 template
> parameter object wrapper:
> 
> * The first testcase demonstrates a situation where the same_type_p
>   assert in relevant case of tsubst_copy doesn't hold, because (partial)
>   substitution of {int,} into the VIEW_CONVERT_EXPR wrapper yields
>   A<int> but substitution into the underlying TEMPLATE_PARM_INDEX is a
>   nop and yields A<T> due to tsubst's level == 1 early exit test.  So
>   this patch just gets rid of the assert; the type mismatch doesn't
>   seem to be a problem in practice, I suppose because the coercion is
>   from one dependent type to another.
> 
> * In the second testcase, dependent substitution into the underlying
>   TEMPLATE_PARM_INDEX yields a CALL_EXPR with empty TREE_TYPE, which
>   tsubst_copy doesn't expect.  This patch fixes this by handling empty
>   TREE_TYPE the same way as a non-const TREE_TYPE.  Moreover, after
>   this substitution we're left with a VIEW_CONVERT_EXPR wrapping a
>   CALL_EXPR instead of a TEMPLATE_PARM_INDEX, which during the subsequent
>   non-dependent substitution tsubst_copy doesn't expect either.  So
>   this patch also relaxes tsubst_copy to accept such VIEW_CONVERT_EXPR
>   too.
> 
> * In the third testcase, we end up never resolving the call to
>   f.modify() since tsubst_copy doesn't do overload resolution.
>   This patch fixes this by moving the handling of these
>   VIEW_CONVERT_EXPR wrappers from tsubst_copy to tsubst_copy_and_build.
>   For good measure tsubst_copy_and_build should also handle
>   REF_PARENTHESIZED_P wrappers instead of delegating to tsubst_copy.
> 
> After this patch, VIEW_CONVERT_EXPR substitution is ultimately just
> moved from tsubst_copy to tsubst_copy_and_build and made more
> permissive.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk?

Ping.

> 
> 	PR c++/103346
> 	PR c++/104278
> 	PR c++/102553
> 
> gcc/cp/ChangeLog:
> 
> 	* pt.cc (tsubst_copy) <case VIEW_CONVERT_EXPR>: In the handling
> 	of C++20 template parameter object wrappers: Remove same_type_p
> 	assert.  Accept non-TEMPLATE_PARM_INDEX inner operand.  Handle
> 	empty TREE_TYPE on substituted inner operand.  Move it to ...
> 	(tsubst_copy_and_build): ... here.  Also handle REF_PARENTHESIZED_P
> 	VIEW_CONVERT_EXPRs.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/nontype-class52a.C: New test.
> 	* g++.dg/cpp2a/nontype-class53.C: New test.
> 	* g++.dg/cpp2a/nontype-class54.C: New test.
> 	* g++.dg/cpp2a/nontype-class55.C: New test.
> ---
>  gcc/cp/pt.cc                                  | 73 ++++++++++---------
>  gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C | 15 ++++
>  gcc/testsuite/g++.dg/cpp2a/nontype-class53.C  | 25 +++++++
>  gcc/testsuite/g++.dg/cpp2a/nontype-class54.C  | 23 ++++++
>  gcc/testsuite/g++.dg/cpp2a/nontype-class55.C  | 15 ++++
>  5 files changed, 116 insertions(+), 35 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 2d8e4fdd4b5..0a196f069ad 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -17271,42 +17271,16 @@ tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>  	      return maybe_wrap_with_location (op0, EXPR_LOCATION (t));
>  	    }
>  	  tree op = TREE_OPERAND (t, 0);
> -	  if (code == VIEW_CONVERT_EXPR
> -	      && TREE_CODE (op) == TEMPLATE_PARM_INDEX)
> -	    {
> -	      /* Wrapper to make a C++20 template parameter object const.  */
> -	      op = tsubst_copy (op, args, complain, in_decl);
> -	      if (!CP_TYPE_CONST_P (TREE_TYPE (op)))
> -		{
> -		  /* The template argument is not const, presumably because
> -		     it is still dependent, and so not the const template parm
> -		     object.  */
> -		  tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
> -		  gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p
> -				       (type, TREE_TYPE (op)));
> -		  if (TREE_CODE (op) == CONSTRUCTOR
> -		      || TREE_CODE (op) == IMPLICIT_CONV_EXPR)
> -		    {
> -		      /* Don't add a wrapper to these.  */
> -		      op = copy_node (op);
> -		      TREE_TYPE (op) = type;
> -		    }
> -		  else
> -		    /* Do add a wrapper otherwise (in particular, if op is
> -		       another TEMPLATE_PARM_INDEX).  */
> -		    op = build1 (code, type, op);
> -		}
> -	      return op;
> -	    }
>  	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
> -	  else if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
> +	  if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
>  	    {
>  	      op = tsubst_copy (op, args, complain, in_decl);
>  	      op = build1 (code, TREE_TYPE (op), op);
>  	      REF_PARENTHESIZED_P (op) = true;
>  	      return op;
>  	    }
> -	  /* We shouldn't see any other uses of these in templates.  */
> +	  /* We shouldn't see any other uses of these in templates
> +	     (tsubst_copy_and_build handles C++20 tparm object wrappers).  */
>  	  gcc_unreachable ();
>  	}
>  
> @@ -21569,12 +21543,41 @@ tsubst_copy_and_build (tree t,
>  
>      case NON_LVALUE_EXPR:
>      case VIEW_CONVERT_EXPR:
> -      if (location_wrapper_p (t))
> -	/* We need to do this here as well as in tsubst_copy so we get the
> -	   other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
> -	RETURN (maybe_wrap_with_location (RECUR (TREE_OPERAND (t, 0)),
> -					  EXPR_LOCATION (t)));
> -      /* fallthrough.  */
> +      {
> +	tree op = RECUR (TREE_OPERAND (t, 0));
> +	if (location_wrapper_p (t))
> +	  /* We need to do this here as well as in tsubst_copy so we get the
> +	     other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
> +	  RETURN (maybe_wrap_with_location (op, EXPR_LOCATION (t)));
> +
> +	gcc_checking_assert (TREE_CODE (t) == VIEW_CONVERT_EXPR);
> +	if (REF_PARENTHESIZED_P (t))
> +	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
> +	  RETURN (finish_parenthesized_expr (op));
> +
> +	/* Otherwise, we're dealing with a wrapper to make a C++20 template
> +	   parameter object const.  */
> +	if (TREE_TYPE (op) == NULL_TREE
> +	    || !CP_TYPE_CONST_P (TREE_TYPE (op)))
> +	  {
> +	    /* The template argument is not const, presumably because
> +	       it is still dependent, and so not the const template parm
> +	       object.  */
> +	    tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
> +	    if (TREE_CODE (op) == CONSTRUCTOR
> +		|| TREE_CODE (op) == IMPLICIT_CONV_EXPR)
> +	      {
> +		/* Don't add a wrapper to these.  */
> +		op = copy_node (op);
> +		TREE_TYPE (op) = type;
> +	      }
> +	    else
> +	      /* Do add a wrapper otherwise (in particular, if op is
> +		 another TEMPLATE_PARM_INDEX).  */
> +	      op = build1 (VIEW_CONVERT_EXPR, type, op);
> +	  }
> +	RETURN (op);
> +      }
>  
>      default:
>        /* Handle Objective-C++ constructs, if appropriate.  */
> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
> new file mode 100644
> index 00000000000..ae5d5df70ac
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
> @@ -0,0 +1,15 @@
> +// A version of nontype-class52.C where explicit template arguments are
> +// given in the call to f (which during deduction need to be partially
> +// substituted into the NTTP object V in f's signature).
> +// { dg-do compile { target c++20 } }
> +
> +template<class> struct A { };
> +
> +template<auto> struct B { };
> +
> +template<class T, A<T> V> void f(B<V>);
> +
> +int main() {
> +  constexpr A<int> a;
> +  f<int>(B<a>{});
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
> new file mode 100644
> index 00000000000..9a6398c5f57
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
> @@ -0,0 +1,25 @@
> +// PR c++/103346
> +// { dg-do compile { target c++20 } }
> +
> +struct Item {};
> +
> +template<class T, T... ts>
> +struct Sequence { };
> +
> +template<Item... items>
> +using ItemSequence = Sequence<Item, items...>;
> +
> +template<Item... items>
> +constexpr auto f() {
> +  constexpr auto l = [](Item item) { return item; };
> +  return ItemSequence<l(items)...>{};
> +}
> +
> +using ty0 = decltype(f<>());
> +using ty0 = ItemSequence<>;
> +
> +using ty1 = decltype(f<{}>());
> +using ty1 = ItemSequence<{}>;
> +
> +using ty3 = decltype(f<{}, {}, {}>());
> +using ty3 = ItemSequence<{}, {}, {}>;
> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
> new file mode 100644
> index 00000000000..8127b1f5426
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
> @@ -0,0 +1,23 @@
> +// PR c++/104278
> +// { dg-do compile { target c++20 } }
> +
> +struct foo {
> +  int value;
> +  constexpr foo modify() const { return { value + 1 }; }
> +};
> +
> +template<foo f, bool Enable = f.value & 1>
> +struct bar {
> +  static void run() { }
> +};
> +
> +template<foo f>
> +struct qux {
> +  static void run() {
> +    bar<f.modify()>::run();
> +  }
> +};
> +
> +int main() {
> +  qux<foo{}>::run();
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> new file mode 100644
> index 00000000000..afcb3d64495
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> @@ -0,0 +1,15 @@
> +// PR c++/102553
> +// { dg-do compile { target c++20 } }
> +
> +struct s1{};
> +template<int> inline constexpr s1 ch{};
> +
> +template<s1 f> struct s2{};
> +template<s1 f> using alias1 = s2<f>;
> +
> +template<class T>
> +void general(int n) {
> +  alias1<ch<1>>{};
> +}
> +
> +template void general<int>(int);
> -- 
> 2.39.0.rc2
> 
>
Jason Merrill Dec. 19, 2022, 5:17 p.m. UTC | #3
On 12/6/22 13:35, Patrick Palka wrote:
> This patch fixes some issues with substitution into a C++20 template
> parameter object wrapper:
> 
> * The first testcase demonstrates a situation where the same_type_p
>    assert in relevant case of tsubst_copy doesn't hold, because (partial)
>    substitution of {int,} into the VIEW_CONVERT_EXPR wrapper yields
>    A<int> but substitution into the underlying TEMPLATE_PARM_INDEX is a
>    nop and yields A<T> due to tsubst's level == 1 early exit test.

We exit in that case because continuing would reduce the level to an 
impossible 0.  Why doesn't the preceding code find a binding for T?

>    So
>    this patch just gets rid of the assert; the type mismatch doesn't
>    seem to be a problem in practice, I suppose because the coercion is
>    from one dependent type to another.
> 
> * In the second testcase, dependent substitution into the underlying
>    TEMPLATE_PARM_INDEX yields a CALL_EXPR with empty TREE_TYPE, which
>    tsubst_copy doesn't expect.  This patch fixes this by handling empty
>    TREE_TYPE the same way as a non-const TREE_TYPE.  Moreover, after
>    this substitution we're left with a VIEW_CONVERT_EXPR wrapping a
>    CALL_EXPR instead of a TEMPLATE_PARM_INDEX, which during the subsequent
>    non-dependent substitution tsubst_copy doesn't expect either.  So
>    this patch also relaxes tsubst_copy to accept such VIEW_CONVERT_EXPR
>    too.
> 
> * In the third testcase, we end up never resolving the call to
>    f.modify() since tsubst_copy doesn't do overload resolution.
>    This patch fixes this by moving the handling of these
>    VIEW_CONVERT_EXPR wrappers from tsubst_copy to tsubst_copy_and_build.
>    For good measure tsubst_copy_and_build should also handle
>    REF_PARENTHESIZED_P wrappers instead of delegating to tsubst_copy.
> 
> After this patch, VIEW_CONVERT_EXPR substitution is ultimately just
> moved from tsubst_copy to tsubst_copy_and_build and made more
> permissive.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk?
> 
> 	PR c++/103346
> 	PR c++/104278
> 	PR c++/102553
> 
> gcc/cp/ChangeLog:
> 
> 	* pt.cc (tsubst_copy) <case VIEW_CONVERT_EXPR>: In the handling
> 	of C++20 template parameter object wrappers: Remove same_type_p
> 	assert.  Accept non-TEMPLATE_PARM_INDEX inner operand.  Handle
> 	empty TREE_TYPE on substituted inner operand.  Move it to ...
> 	(tsubst_copy_and_build): ... here.  Also handle REF_PARENTHESIZED_P
> 	VIEW_CONVERT_EXPRs.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/nontype-class52a.C: New test.
> 	* g++.dg/cpp2a/nontype-class53.C: New test.
> 	* g++.dg/cpp2a/nontype-class54.C: New test.
> 	* g++.dg/cpp2a/nontype-class55.C: New test.
> ---
>   gcc/cp/pt.cc                                  | 73 ++++++++++---------
>   gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C | 15 ++++
>   gcc/testsuite/g++.dg/cpp2a/nontype-class53.C  | 25 +++++++
>   gcc/testsuite/g++.dg/cpp2a/nontype-class54.C  | 23 ++++++
>   gcc/testsuite/g++.dg/cpp2a/nontype-class55.C  | 15 ++++
>   5 files changed, 116 insertions(+), 35 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 2d8e4fdd4b5..0a196f069ad 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -17271,42 +17271,16 @@ tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   	      return maybe_wrap_with_location (op0, EXPR_LOCATION (t));
>   	    }
>   	  tree op = TREE_OPERAND (t, 0);
> -	  if (code == VIEW_CONVERT_EXPR
> -	      && TREE_CODE (op) == TEMPLATE_PARM_INDEX)
> -	    {
> -	      /* Wrapper to make a C++20 template parameter object const.  */
> -	      op = tsubst_copy (op, args, complain, in_decl);
> -	      if (!CP_TYPE_CONST_P (TREE_TYPE (op)))
> -		{
> -		  /* The template argument is not const, presumably because
> -		     it is still dependent, and so not the const template parm
> -		     object.  */
> -		  tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
> -		  gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p
> -				       (type, TREE_TYPE (op)));
> -		  if (TREE_CODE (op) == CONSTRUCTOR
> -		      || TREE_CODE (op) == IMPLICIT_CONV_EXPR)
> -		    {
> -		      /* Don't add a wrapper to these.  */
> -		      op = copy_node (op);
> -		      TREE_TYPE (op) = type;
> -		    }
> -		  else
> -		    /* Do add a wrapper otherwise (in particular, if op is
> -		       another TEMPLATE_PARM_INDEX).  */
> -		    op = build1 (code, type, op);
> -		}
> -	      return op;
> -	    }
>   	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
> -	  else if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
> +	  if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
>   	    {
>   	      op = tsubst_copy (op, args, complain, in_decl);
>   	      op = build1 (code, TREE_TYPE (op), op);
>   	      REF_PARENTHESIZED_P (op) = true;
>   	      return op;
>   	    }
> -	  /* We shouldn't see any other uses of these in templates.  */
> +	  /* We shouldn't see any other uses of these in templates
> +	     (tsubst_copy_and_build handles C++20 tparm object wrappers).  */
>   	  gcc_unreachable ();
>   	}
>   
> @@ -21569,12 +21543,41 @@ tsubst_copy_and_build (tree t,
>   
>       case NON_LVALUE_EXPR:
>       case VIEW_CONVERT_EXPR:
> -      if (location_wrapper_p (t))
> -	/* We need to do this here as well as in tsubst_copy so we get the
> -	   other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
> -	RETURN (maybe_wrap_with_location (RECUR (TREE_OPERAND (t, 0)),
> -					  EXPR_LOCATION (t)));
> -      /* fallthrough.  */
> +      {
> +	tree op = RECUR (TREE_OPERAND (t, 0));
> +	if (location_wrapper_p (t))
> +	  /* We need to do this here as well as in tsubst_copy so we get the
> +	     other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
> +	  RETURN (maybe_wrap_with_location (op, EXPR_LOCATION (t)));
> +
> +	gcc_checking_assert (TREE_CODE (t) == VIEW_CONVERT_EXPR);
> +	if (REF_PARENTHESIZED_P (t))
> +	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
> +	  RETURN (finish_parenthesized_expr (op));
> +
> +	/* Otherwise, we're dealing with a wrapper to make a C++20 template
> +	   parameter object const.  */
> +	if (TREE_TYPE (op) == NULL_TREE
> +	    || !CP_TYPE_CONST_P (TREE_TYPE (op)))
> +	  {
> +	    /* The template argument is not const, presumably because
> +	       it is still dependent, and so not the const template parm
> +	       object.  */
> +	    tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
> +	    if (TREE_CODE (op) == CONSTRUCTOR
> +		|| TREE_CODE (op) == IMPLICIT_CONV_EXPR)
> +	      {
> +		/* Don't add a wrapper to these.  */
> +		op = copy_node (op);
> +		TREE_TYPE (op) = type;
> +	      }
> +	    else
> +	      /* Do add a wrapper otherwise (in particular, if op is
> +		 another TEMPLATE_PARM_INDEX).  */
> +	      op = build1 (VIEW_CONVERT_EXPR, type, op);
> +	  }
> +	RETURN (op);
> +      }
>   
>       default:
>         /* Handle Objective-C++ constructs, if appropriate.  */
> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
> new file mode 100644
> index 00000000000..ae5d5df70ac
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
> @@ -0,0 +1,15 @@
> +// A version of nontype-class52.C where explicit template arguments are
> +// given in the call to f (which during deduction need to be partially
> +// substituted into the NTTP object V in f's signature).
> +// { dg-do compile { target c++20 } }
> +
> +template<class> struct A { };
> +
> +template<auto> struct B { };
> +
> +template<class T, A<T> V> void f(B<V>);
> +
> +int main() {
> +  constexpr A<int> a;
> +  f<int>(B<a>{});
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
> new file mode 100644
> index 00000000000..9a6398c5f57
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
> @@ -0,0 +1,25 @@
> +// PR c++/103346
> +// { dg-do compile { target c++20 } }
> +
> +struct Item {};
> +
> +template<class T, T... ts>
> +struct Sequence { };
> +
> +template<Item... items>
> +using ItemSequence = Sequence<Item, items...>;
> +
> +template<Item... items>
> +constexpr auto f() {
> +  constexpr auto l = [](Item item) { return item; };
> +  return ItemSequence<l(items)...>{};
> +}
> +
> +using ty0 = decltype(f<>());
> +using ty0 = ItemSequence<>;
> +
> +using ty1 = decltype(f<{}>());
> +using ty1 = ItemSequence<{}>;
> +
> +using ty3 = decltype(f<{}, {}, {}>());
> +using ty3 = ItemSequence<{}, {}, {}>;
> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
> new file mode 100644
> index 00000000000..8127b1f5426
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
> @@ -0,0 +1,23 @@
> +// PR c++/104278
> +// { dg-do compile { target c++20 } }
> +
> +struct foo {
> +  int value;
> +  constexpr foo modify() const { return { value + 1 }; }
> +};
> +
> +template<foo f, bool Enable = f.value & 1>
> +struct bar {
> +  static void run() { }
> +};
> +
> +template<foo f>
> +struct qux {
> +  static void run() {
> +    bar<f.modify()>::run();
> +  }
> +};
> +
> +int main() {
> +  qux<foo{}>::run();
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> new file mode 100644
> index 00000000000..afcb3d64495
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> @@ -0,0 +1,15 @@
> +// PR c++/102553
> +// { dg-do compile { target c++20 } }
> +
> +struct s1{};
> +template<int> inline constexpr s1 ch{};
> +
> +template<s1 f> struct s2{};
> +template<s1 f> using alias1 = s2<f>;
> +
> +template<class T>
> +void general(int n) {
> +  alias1<ch<1>>{};
> +}
> +
> +template void general<int>(int);
Patrick Palka Dec. 19, 2022, 6:13 p.m. UTC | #4
On Mon, 19 Dec 2022, Jason Merrill wrote:

> On 12/6/22 13:35, Patrick Palka wrote:
> > This patch fixes some issues with substitution into a C++20 template
> > parameter object wrapper:
> > 
> > * The first testcase demonstrates a situation where the same_type_p
> >    assert in relevant case of tsubst_copy doesn't hold, because (partial)
> >    substitution of {int,} into the VIEW_CONVERT_EXPR wrapper yields
> >    A<int> but substitution into the underlying TEMPLATE_PARM_INDEX is a
> >    nop and yields A<T> due to tsubst's level == 1 early exit test.
> 
> We exit in that case because continuing would reduce the level to an
> impossible 0.  Why doesn't the preceding code find a binding for T?

Whoops, I misspoke.  The problem is that there's no binding for V since
only T=int was explicitly specified, so when substituting into the
TEMPLATE_PARM_INDEX for V in 'void f(B<V>)', we hit that early exit and
never get a chance to substitute T=int into the tpi's TREE_TYPE (which
would yield A<int> as desired).  So the TREE_TYPE of the wrapped tpi
remains A<T> whereas the substituted TREE_TYPE of the wrapper is A<int>,
a mismatch.

> 
> >    So
> >    this patch just gets rid of the assert; the type mismatch doesn't
> >    seem to be a problem in practice, I suppose because the coercion is
> >    from one dependent type to another.
> > 
> > * In the second testcase, dependent substitution into the underlying
> >    TEMPLATE_PARM_INDEX yields a CALL_EXPR with empty TREE_TYPE, which
> >    tsubst_copy doesn't expect.  This patch fixes this by handling empty
> >    TREE_TYPE the same way as a non-const TREE_TYPE.  Moreover, after
> >    this substitution we're left with a VIEW_CONVERT_EXPR wrapping a
> >    CALL_EXPR instead of a TEMPLATE_PARM_INDEX, which during the subsequent
> >    non-dependent substitution tsubst_copy doesn't expect either.  So
> >    this patch also relaxes tsubst_copy to accept such VIEW_CONVERT_EXPR
> >    too.
> > 
> > * In the third testcase, we end up never resolving the call to
> >    f.modify() since tsubst_copy doesn't do overload resolution.
> >    This patch fixes this by moving the handling of these
> >    VIEW_CONVERT_EXPR wrappers from tsubst_copy to tsubst_copy_and_build.
> >    For good measure tsubst_copy_and_build should also handle
> >    REF_PARENTHESIZED_P wrappers instead of delegating to tsubst_copy.
> > 
> > After this patch, VIEW_CONVERT_EXPR substitution is ultimately just
> > moved from tsubst_copy to tsubst_copy_and_build and made more
> > permissive.
> > 
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > trunk?
> > 
> > 	PR c++/103346
> > 	PR c++/104278
> > 	PR c++/102553
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* pt.cc (tsubst_copy) <case VIEW_CONVERT_EXPR>: In the handling
> > 	of C++20 template parameter object wrappers: Remove same_type_p
> > 	assert.  Accept non-TEMPLATE_PARM_INDEX inner operand.  Handle
> > 	empty TREE_TYPE on substituted inner operand.  Move it to ...
> > 	(tsubst_copy_and_build): ... here.  Also handle REF_PARENTHESIZED_P
> > 	VIEW_CONVERT_EXPRs.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/cpp2a/nontype-class52a.C: New test.
> > 	* g++.dg/cpp2a/nontype-class53.C: New test.
> > 	* g++.dg/cpp2a/nontype-class54.C: New test.
> > 	* g++.dg/cpp2a/nontype-class55.C: New test.
> > ---
> >   gcc/cp/pt.cc                                  | 73 ++++++++++---------
> >   gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C | 15 ++++
> >   gcc/testsuite/g++.dg/cpp2a/nontype-class53.C  | 25 +++++++
> >   gcc/testsuite/g++.dg/cpp2a/nontype-class54.C  | 23 ++++++
> >   gcc/testsuite/g++.dg/cpp2a/nontype-class55.C  | 15 ++++
> >   5 files changed, 116 insertions(+), 35 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> > 
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 2d8e4fdd4b5..0a196f069ad 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -17271,42 +17271,16 @@ tsubst_copy (tree t, tree args, tsubst_flags_t
> > complain, tree in_decl)
> >   	      return maybe_wrap_with_location (op0, EXPR_LOCATION (t));
> >   	    }
> >   	  tree op = TREE_OPERAND (t, 0);
> > -	  if (code == VIEW_CONVERT_EXPR
> > -	      && TREE_CODE (op) == TEMPLATE_PARM_INDEX)
> > -	    {
> > -	      /* Wrapper to make a C++20 template parameter object const.  */
> > -	      op = tsubst_copy (op, args, complain, in_decl);
> > -	      if (!CP_TYPE_CONST_P (TREE_TYPE (op)))
> > -		{
> > -		  /* The template argument is not const, presumably because
> > -		     it is still dependent, and so not the const template parm
> > -		     object.  */
> > -		  tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
> > -		  gcc_checking_assert
> > (same_type_ignoring_top_level_qualifiers_p
> > -				       (type, TREE_TYPE (op)));
> > -		  if (TREE_CODE (op) == CONSTRUCTOR
> > -		      || TREE_CODE (op) == IMPLICIT_CONV_EXPR)
> > -		    {
> > -		      /* Don't add a wrapper to these.  */
> > -		      op = copy_node (op);
> > -		      TREE_TYPE (op) = type;
> > -		    }
> > -		  else
> > -		    /* Do add a wrapper otherwise (in particular, if op is
> > -		       another TEMPLATE_PARM_INDEX).  */
> > -		    op = build1 (code, type, op);
> > -		}
> > -	      return op;
> > -	    }
> >   	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
> > -	  else if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
> > +	  if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
> >   	    {
> >   	      op = tsubst_copy (op, args, complain, in_decl);
> >   	      op = build1 (code, TREE_TYPE (op), op);
> >   	      REF_PARENTHESIZED_P (op) = true;
> >   	      return op;
> >   	    }
> > -	  /* We shouldn't see any other uses of these in templates.  */
> > +	  /* We shouldn't see any other uses of these in templates
> > +	     (tsubst_copy_and_build handles C++20 tparm object wrappers).  */
> >   	  gcc_unreachable ();
> >   	}
> >   @@ -21569,12 +21543,41 @@ tsubst_copy_and_build (tree t,
> >         case NON_LVALUE_EXPR:
> >       case VIEW_CONVERT_EXPR:
> > -      if (location_wrapper_p (t))
> > -	/* We need to do this here as well as in tsubst_copy so we get the
> > -	   other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
> > -	RETURN (maybe_wrap_with_location (RECUR (TREE_OPERAND (t, 0)),
> > -					  EXPR_LOCATION (t)));
> > -      /* fallthrough.  */
> > +      {
> > +	tree op = RECUR (TREE_OPERAND (t, 0));
> > +	if (location_wrapper_p (t))
> > +	  /* We need to do this here as well as in tsubst_copy so we get the
> > +	     other tsubst_copy_and_build semantics for a PARM_DECL operand.
> > */
> > +	  RETURN (maybe_wrap_with_location (op, EXPR_LOCATION (t)));
> > +
> > +	gcc_checking_assert (TREE_CODE (t) == VIEW_CONVERT_EXPR);
> > +	if (REF_PARENTHESIZED_P (t))
> > +	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
> > +	  RETURN (finish_parenthesized_expr (op));
> > +
> > +	/* Otherwise, we're dealing with a wrapper to make a C++20 template
> > +	   parameter object const.  */
> > +	if (TREE_TYPE (op) == NULL_TREE
> > +	    || !CP_TYPE_CONST_P (TREE_TYPE (op)))
> > +	  {
> > +	    /* The template argument is not const, presumably because
> > +	       it is still dependent, and so not the const template parm
> > +	       object.  */
> > +	    tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
> > +	    if (TREE_CODE (op) == CONSTRUCTOR
> > +		|| TREE_CODE (op) == IMPLICIT_CONV_EXPR)
> > +	      {
> > +		/* Don't add a wrapper to these.  */
> > +		op = copy_node (op);
> > +		TREE_TYPE (op) = type;
> > +	      }
> > +	    else
> > +	      /* Do add a wrapper otherwise (in particular, if op is
> > +		 another TEMPLATE_PARM_INDEX).  */
> > +	      op = build1 (VIEW_CONVERT_EXPR, type, op);
> > +	  }
> > +	RETURN (op);
> > +      }
> >         default:
> >         /* Handle Objective-C++ constructs, if appropriate.  */
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
> > b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
> > new file mode 100644
> > index 00000000000..ae5d5df70ac
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
> > @@ -0,0 +1,15 @@
> > +// A version of nontype-class52.C where explicit template arguments are
> > +// given in the call to f (which during deduction need to be partially
> > +// substituted into the NTTP object V in f's signature).
> > +// { dg-do compile { target c++20 } }
> > +
> > +template<class> struct A { };
> > +
> > +template<auto> struct B { };
> > +
> > +template<class T, A<T> V> void f(B<V>);
> > +
> > +int main() {
> > +  constexpr A<int> a;
> > +  f<int>(B<a>{});
> > +}
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
> > b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
> > new file mode 100644
> > index 00000000000..9a6398c5f57
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
> > @@ -0,0 +1,25 @@
> > +// PR c++/103346
> > +// { dg-do compile { target c++20 } }
> > +
> > +struct Item {};
> > +
> > +template<class T, T... ts>
> > +struct Sequence { };
> > +
> > +template<Item... items>
> > +using ItemSequence = Sequence<Item, items...>;
> > +
> > +template<Item... items>
> > +constexpr auto f() {
> > +  constexpr auto l = [](Item item) { return item; };
> > +  return ItemSequence<l(items)...>{};
> > +}
> > +
> > +using ty0 = decltype(f<>());
> > +using ty0 = ItemSequence<>;
> > +
> > +using ty1 = decltype(f<{}>());
> > +using ty1 = ItemSequence<{}>;
> > +
> > +using ty3 = decltype(f<{}, {}, {}>());
> > +using ty3 = ItemSequence<{}, {}, {}>;
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
> > b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
> > new file mode 100644
> > index 00000000000..8127b1f5426
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
> > @@ -0,0 +1,23 @@
> > +// PR c++/104278
> > +// { dg-do compile { target c++20 } }
> > +
> > +struct foo {
> > +  int value;
> > +  constexpr foo modify() const { return { value + 1 }; }
> > +};
> > +
> > +template<foo f, bool Enable = f.value & 1>
> > +struct bar {
> > +  static void run() { }
> > +};
> > +
> > +template<foo f>
> > +struct qux {
> > +  static void run() {
> > +    bar<f.modify()>::run();
> > +  }
> > +};
> > +
> > +int main() {
> > +  qux<foo{}>::run();
> > +}
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> > b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> > new file mode 100644
> > index 00000000000..afcb3d64495
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
> > @@ -0,0 +1,15 @@
> > +// PR c++/102553
> > +// { dg-do compile { target c++20 } }
> > +
> > +struct s1{};
> > +template<int> inline constexpr s1 ch{};
> > +
> > +template<s1 f> struct s2{};
> > +template<s1 f> using alias1 = s2<f>;
> > +
> > +template<class T>
> > +void general(int n) {
> > +  alias1<ch<1>>{};
> > +}
> > +
> > +template void general<int>(int);
> 
>
Jason Merrill Dec. 19, 2022, 7:56 p.m. UTC | #5
On 12/19/22 13:13, Patrick Palka wrote:
> On Mon, 19 Dec 2022, Jason Merrill wrote:
> 
>> On 12/6/22 13:35, Patrick Palka wrote:
>>> This patch fixes some issues with substitution into a C++20 template
>>> parameter object wrapper:
>>>
>>> * The first testcase demonstrates a situation where the same_type_p
>>>     assert in relevant case of tsubst_copy doesn't hold, because (partial)
>>>     substitution of {int,} into the VIEW_CONVERT_EXPR wrapper yields
>>>     A<int> but substitution into the underlying TEMPLATE_PARM_INDEX is a
>>>     nop and yields A<T> due to tsubst's level == 1 early exit test.
>>
>> We exit in that case because continuing would reduce the level to an
>> impossible 0.  Why doesn't the preceding code find a binding for T?
> 
> Whoops, I misspoke.  The problem is that there's no binding for V since
> only T=int was explicitly specified, so when substituting into the
> TEMPLATE_PARM_INDEX for V in 'void f(B<V>)', we hit that early exit and
> never get a chance to substitute T=int into the tpi's TREE_TYPE (which
> would yield A<int> as desired).  So the TREE_TYPE of the wrapped tpi
> remains A<T> whereas the substituted TREE_TYPE of the wrapper is A<int>,
> a mismatch.

Ah, makes sense.  The patch is OK.

>>
>>>     So
>>>     this patch just gets rid of the assert; the type mismatch doesn't
>>>     seem to be a problem in practice, I suppose because the coercion is
>>>     from one dependent type to another.
>>>
>>> * In the second testcase, dependent substitution into the underlying
>>>     TEMPLATE_PARM_INDEX yields a CALL_EXPR with empty TREE_TYPE, which
>>>     tsubst_copy doesn't expect.  This patch fixes this by handling empty
>>>     TREE_TYPE the same way as a non-const TREE_TYPE.  Moreover, after
>>>     this substitution we're left with a VIEW_CONVERT_EXPR wrapping a
>>>     CALL_EXPR instead of a TEMPLATE_PARM_INDEX, which during the subsequent
>>>     non-dependent substitution tsubst_copy doesn't expect either.  So
>>>     this patch also relaxes tsubst_copy to accept such VIEW_CONVERT_EXPR
>>>     too.
>>>
>>> * In the third testcase, we end up never resolving the call to
>>>     f.modify() since tsubst_copy doesn't do overload resolution.
>>>     This patch fixes this by moving the handling of these
>>>     VIEW_CONVERT_EXPR wrappers from tsubst_copy to tsubst_copy_and_build.
>>>     For good measure tsubst_copy_and_build should also handle
>>>     REF_PARENTHESIZED_P wrappers instead of delegating to tsubst_copy.
>>>
>>> After this patch, VIEW_CONVERT_EXPR substitution is ultimately just
>>> moved from tsubst_copy to tsubst_copy_and_build and made more
>>> permissive.
>>>
>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
>>> trunk?
>>>
>>> 	PR c++/103346
>>> 	PR c++/104278
>>> 	PR c++/102553
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* pt.cc (tsubst_copy) <case VIEW_CONVERT_EXPR>: In the handling
>>> 	of C++20 template parameter object wrappers: Remove same_type_p
>>> 	assert.  Accept non-TEMPLATE_PARM_INDEX inner operand.  Handle
>>> 	empty TREE_TYPE on substituted inner operand.  Move it to ...
>>> 	(tsubst_copy_and_build): ... here.  Also handle REF_PARENTHESIZED_P
>>> 	VIEW_CONVERT_EXPRs.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/cpp2a/nontype-class52a.C: New test.
>>> 	* g++.dg/cpp2a/nontype-class53.C: New test.
>>> 	* g++.dg/cpp2a/nontype-class54.C: New test.
>>> 	* g++.dg/cpp2a/nontype-class55.C: New test.
>>> ---
>>>    gcc/cp/pt.cc                                  | 73 ++++++++++---------
>>>    gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C | 15 ++++
>>>    gcc/testsuite/g++.dg/cpp2a/nontype-class53.C  | 25 +++++++
>>>    gcc/testsuite/g++.dg/cpp2a/nontype-class54.C  | 23 ++++++
>>>    gcc/testsuite/g++.dg/cpp2a/nontype-class55.C  | 15 ++++
>>>    5 files changed, 116 insertions(+), 35 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
>>>
>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>> index 2d8e4fdd4b5..0a196f069ad 100644
>>> --- a/gcc/cp/pt.cc
>>> +++ b/gcc/cp/pt.cc
>>> @@ -17271,42 +17271,16 @@ tsubst_copy (tree t, tree args, tsubst_flags_t
>>> complain, tree in_decl)
>>>    	      return maybe_wrap_with_location (op0, EXPR_LOCATION (t));
>>>    	    }
>>>    	  tree op = TREE_OPERAND (t, 0);
>>> -	  if (code == VIEW_CONVERT_EXPR
>>> -	      && TREE_CODE (op) == TEMPLATE_PARM_INDEX)
>>> -	    {
>>> -	      /* Wrapper to make a C++20 template parameter object const.  */
>>> -	      op = tsubst_copy (op, args, complain, in_decl);
>>> -	      if (!CP_TYPE_CONST_P (TREE_TYPE (op)))
>>> -		{
>>> -		  /* The template argument is not const, presumably because
>>> -		     it is still dependent, and so not the const template parm
>>> -		     object.  */
>>> -		  tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
>>> -		  gcc_checking_assert
>>> (same_type_ignoring_top_level_qualifiers_p
>>> -				       (type, TREE_TYPE (op)));
>>> -		  if (TREE_CODE (op) == CONSTRUCTOR
>>> -		      || TREE_CODE (op) == IMPLICIT_CONV_EXPR)
>>> -		    {
>>> -		      /* Don't add a wrapper to these.  */
>>> -		      op = copy_node (op);
>>> -		      TREE_TYPE (op) = type;
>>> -		    }
>>> -		  else
>>> -		    /* Do add a wrapper otherwise (in particular, if op is
>>> -		       another TEMPLATE_PARM_INDEX).  */
>>> -		    op = build1 (code, type, op);
>>> -		}
>>> -	      return op;
>>> -	    }
>>>    	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
>>> -	  else if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
>>> +	  if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
>>>    	    {
>>>    	      op = tsubst_copy (op, args, complain, in_decl);
>>>    	      op = build1 (code, TREE_TYPE (op), op);
>>>    	      REF_PARENTHESIZED_P (op) = true;
>>>    	      return op;
>>>    	    }
>>> -	  /* We shouldn't see any other uses of these in templates.  */
>>> +	  /* We shouldn't see any other uses of these in templates
>>> +	     (tsubst_copy_and_build handles C++20 tparm object wrappers).  */
>>>    	  gcc_unreachable ();
>>>    	}
>>>    @@ -21569,12 +21543,41 @@ tsubst_copy_and_build (tree t,
>>>          case NON_LVALUE_EXPR:
>>>        case VIEW_CONVERT_EXPR:
>>> -      if (location_wrapper_p (t))
>>> -	/* We need to do this here as well as in tsubst_copy so we get the
>>> -	   other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
>>> -	RETURN (maybe_wrap_with_location (RECUR (TREE_OPERAND (t, 0)),
>>> -					  EXPR_LOCATION (t)));
>>> -      /* fallthrough.  */
>>> +      {
>>> +	tree op = RECUR (TREE_OPERAND (t, 0));
>>> +	if (location_wrapper_p (t))
>>> +	  /* We need to do this here as well as in tsubst_copy so we get the
>>> +	     other tsubst_copy_and_build semantics for a PARM_DECL operand.
>>> */
>>> +	  RETURN (maybe_wrap_with_location (op, EXPR_LOCATION (t)));
>>> +
>>> +	gcc_checking_assert (TREE_CODE (t) == VIEW_CONVERT_EXPR);
>>> +	if (REF_PARENTHESIZED_P (t))
>>> +	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
>>> +	  RETURN (finish_parenthesized_expr (op));
>>> +
>>> +	/* Otherwise, we're dealing with a wrapper to make a C++20 template
>>> +	   parameter object const.  */
>>> +	if (TREE_TYPE (op) == NULL_TREE
>>> +	    || !CP_TYPE_CONST_P (TREE_TYPE (op)))
>>> +	  {
>>> +	    /* The template argument is not const, presumably because
>>> +	       it is still dependent, and so not the const template parm
>>> +	       object.  */
>>> +	    tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
>>> +	    if (TREE_CODE (op) == CONSTRUCTOR
>>> +		|| TREE_CODE (op) == IMPLICIT_CONV_EXPR)
>>> +	      {
>>> +		/* Don't add a wrapper to these.  */
>>> +		op = copy_node (op);
>>> +		TREE_TYPE (op) = type;
>>> +	      }
>>> +	    else
>>> +	      /* Do add a wrapper otherwise (in particular, if op is
>>> +		 another TEMPLATE_PARM_INDEX).  */
>>> +	      op = build1 (VIEW_CONVERT_EXPR, type, op);
>>> +	  }
>>> +	RETURN (op);
>>> +      }
>>>          default:
>>>          /* Handle Objective-C++ constructs, if appropriate.  */
>>> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
>>> b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
>>> new file mode 100644
>>> index 00000000000..ae5d5df70ac
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
>>> @@ -0,0 +1,15 @@
>>> +// A version of nontype-class52.C where explicit template arguments are
>>> +// given in the call to f (which during deduction need to be partially
>>> +// substituted into the NTTP object V in f's signature).
>>> +// { dg-do compile { target c++20 } }
>>> +
>>> +template<class> struct A { };
>>> +
>>> +template<auto> struct B { };
>>> +
>>> +template<class T, A<T> V> void f(B<V>);
>>> +
>>> +int main() {
>>> +  constexpr A<int> a;
>>> +  f<int>(B<a>{});
>>> +}
>>> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
>>> b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
>>> new file mode 100644
>>> index 00000000000..9a6398c5f57
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
>>> @@ -0,0 +1,25 @@
>>> +// PR c++/103346
>>> +// { dg-do compile { target c++20 } }
>>> +
>>> +struct Item {};
>>> +
>>> +template<class T, T... ts>
>>> +struct Sequence { };
>>> +
>>> +template<Item... items>
>>> +using ItemSequence = Sequence<Item, items...>;
>>> +
>>> +template<Item... items>
>>> +constexpr auto f() {
>>> +  constexpr auto l = [](Item item) { return item; };
>>> +  return ItemSequence<l(items)...>{};
>>> +}
>>> +
>>> +using ty0 = decltype(f<>());
>>> +using ty0 = ItemSequence<>;
>>> +
>>> +using ty1 = decltype(f<{}>());
>>> +using ty1 = ItemSequence<{}>;
>>> +
>>> +using ty3 = decltype(f<{}, {}, {}>());
>>> +using ty3 = ItemSequence<{}, {}, {}>;
>>> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
>>> b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
>>> new file mode 100644
>>> index 00000000000..8127b1f5426
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
>>> @@ -0,0 +1,23 @@
>>> +// PR c++/104278
>>> +// { dg-do compile { target c++20 } }
>>> +
>>> +struct foo {
>>> +  int value;
>>> +  constexpr foo modify() const { return { value + 1 }; }
>>> +};
>>> +
>>> +template<foo f, bool Enable = f.value & 1>
>>> +struct bar {
>>> +  static void run() { }
>>> +};
>>> +
>>> +template<foo f>
>>> +struct qux {
>>> +  static void run() {
>>> +    bar<f.modify()>::run();
>>> +  }
>>> +};
>>> +
>>> +int main() {
>>> +  qux<foo{}>::run();
>>> +}
>>> diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
>>> b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
>>> new file mode 100644
>>> index 00000000000..afcb3d64495
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
>>> @@ -0,0 +1,15 @@
>>> +// PR c++/102553
>>> +// { dg-do compile { target c++20 } }
>>> +
>>> +struct s1{};
>>> +template<int> inline constexpr s1 ch{};
>>> +
>>> +template<s1 f> struct s2{};
>>> +template<s1 f> using alias1 = s2<f>;
>>> +
>>> +template<class T>
>>> +void general(int n) {
>>> +  alias1<ch<1>>{};
>>> +}
>>> +
>>> +template void general<int>(int);
>>
>>
>
diff mbox series

Patch

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 2d8e4fdd4b5..021c2be9257 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -17257,59 +17257,6 @@  tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	/* Ordinary template template argument.  */
 	return t;
 
-    case NON_LVALUE_EXPR:
-    case VIEW_CONVERT_EXPR:
-	{
-	  /* Handle location wrappers by substituting the wrapped node
-	     first, *then* reusing the resulting type.  Doing the type
-	     first ensures that we handle template parameters and
-	     parameter pack expansions.  */
-	  if (location_wrapper_p (t))
-	    {
-	      tree op0 = tsubst_copy (TREE_OPERAND (t, 0), args,
-				      complain, in_decl);
-	      return maybe_wrap_with_location (op0, EXPR_LOCATION (t));
-	    }
-	  tree op = TREE_OPERAND (t, 0);
-	  if (code == VIEW_CONVERT_EXPR
-	      && TREE_CODE (op) == TEMPLATE_PARM_INDEX)
-	    {
-	      /* Wrapper to make a C++20 template parameter object const.  */
-	      op = tsubst_copy (op, args, complain, in_decl);
-	      if (!CP_TYPE_CONST_P (TREE_TYPE (op)))
-		{
-		  /* The template argument is not const, presumably because
-		     it is still dependent, and so not the const template parm
-		     object.  */
-		  tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
-		  gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p
-				       (type, TREE_TYPE (op)));
-		  if (TREE_CODE (op) == CONSTRUCTOR
-		      || TREE_CODE (op) == IMPLICIT_CONV_EXPR)
-		    {
-		      /* Don't add a wrapper to these.  */
-		      op = copy_node (op);
-		      TREE_TYPE (op) = type;
-		    }
-		  else
-		    /* Do add a wrapper otherwise (in particular, if op is
-		       another TEMPLATE_PARM_INDEX).  */
-		    op = build1 (code, type, op);
-		}
-	      return op;
-	    }
-	  /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
-	  else if (code == VIEW_CONVERT_EXPR && REF_PARENTHESIZED_P (t))
-	    {
-	      op = tsubst_copy (op, args, complain, in_decl);
-	      op = build1 (code, TREE_TYPE (op), op);
-	      REF_PARENTHESIZED_P (op) = true;
-	      return op;
-	    }
-	  /* We shouldn't see any other uses of these in templates.  */
-	  gcc_unreachable ();
-	}
-
     case CAST_EXPR:
     case REINTERPRET_CAST_EXPR:
     case CONST_CAST_EXPR:
@@ -21568,13 +21515,48 @@  tsubst_copy_and_build (tree t,
       RETURN (t);
 
     case NON_LVALUE_EXPR:
+      gcc_checking_assert (location_wrapper_p (t));
+      RETURN (maybe_wrap_with_location (RECUR (TREE_OPERAND (t, 0)),
+					EXPR_LOCATION (t)));
+
     case VIEW_CONVERT_EXPR:
-      if (location_wrapper_p (t))
-	/* We need to do this here as well as in tsubst_copy so we get the
-	   other tsubst_copy_and_build semantics for a PARM_DECL operand.  */
-	RETURN (maybe_wrap_with_location (RECUR (TREE_OPERAND (t, 0)),
-					  EXPR_LOCATION (t)));
-      /* fallthrough.  */
+      {
+	if (location_wrapper_p (t))
+	  RETURN (maybe_wrap_with_location (RECUR (TREE_OPERAND (t, 0)),
+					    EXPR_LOCATION (t)));
+	tree op = TREE_OPERAND (t, 0);
+	if (REF_PARENTHESIZED_P (t))
+	  {
+	    /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
+	    op = RECUR (op);
+	    op = build1 (VIEW_CONVERT_EXPR, TREE_TYPE (op), op);
+	    REF_PARENTHESIZED_P (op) = true;
+	    RETURN (op);
+	  }
+	/* We're dealing with a wrapper to make a C++20 template parameter
+	   object const.  */
+	op = RECUR (op);
+	if (TREE_TYPE (op) == NULL_TREE
+	    || !CP_TYPE_CONST_P (TREE_TYPE (op)))
+	  {
+	    /* The template argument is not const, presumably because
+	       it is still dependent, and so not the const template parm
+	       object.  */
+	    tree type = tsubst (TREE_TYPE (t), args, complain, in_decl);
+	    if (TREE_CODE (op) == CONSTRUCTOR
+		|| TREE_CODE (op) == IMPLICIT_CONV_EXPR)
+	      {
+		/* Don't add a wrapper to these.  */
+		op = copy_node (op);
+		TREE_TYPE (op) = type;
+	      }
+	    else
+	      /* Do add a wrapper otherwise (in particular, if op is
+		 another TEMPLATE_PARM_INDEX).  */
+	      op = build1 (VIEW_CONVERT_EXPR, type, op);
+	  }
+	  RETURN (op);
+      }
 
     default:
       /* Handle Objective-C++ constructs, if appropriate.  */
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
new file mode 100644
index 00000000000..ae5d5df70ac
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class52a.C
@@ -0,0 +1,15 @@ 
+// A version of nontype-class52.C where explicit template arguments are
+// given in the call to f (which during deduction need to be partially
+// substituted into the NTTP object V in f's signature).
+// { dg-do compile { target c++20 } }
+
+template<class> struct A { };
+
+template<auto> struct B { };
+
+template<class T, A<T> V> void f(B<V>);
+
+int main() {
+  constexpr A<int> a;
+  f<int>(B<a>{});
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
new file mode 100644
index 00000000000..9a6398c5f57
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class53.C
@@ -0,0 +1,25 @@ 
+// PR c++/103346
+// { dg-do compile { target c++20 } }
+
+struct Item {};
+
+template<class T, T... ts>
+struct Sequence { };
+
+template<Item... items>
+using ItemSequence = Sequence<Item, items...>;
+
+template<Item... items>
+constexpr auto f() {
+  constexpr auto l = [](Item item) { return item; };
+  return ItemSequence<l(items)...>{};
+}
+
+using ty0 = decltype(f<>());
+using ty0 = ItemSequence<>;
+
+using ty1 = decltype(f<{}>());
+using ty1 = ItemSequence<{}>;
+
+using ty3 = decltype(f<{}, {}, {}>());
+using ty3 = ItemSequence<{}, {}, {}>;
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
new file mode 100644
index 00000000000..8127b1f5426
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class54.C
@@ -0,0 +1,23 @@ 
+// PR c++/104278
+// { dg-do compile { target c++20 } }
+
+struct foo {
+  int value;
+  constexpr foo modify() const { return { value + 1 }; }
+};
+
+template<foo f, bool Enable = f.value & 1>
+struct bar {
+  static void run() { }
+};
+
+template<foo f>
+struct qux {
+  static void run() {
+    bar<f.modify()>::run();
+  }
+};
+
+int main() {
+  qux<foo{}>::run();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
new file mode 100644
index 00000000000..afcb3d64495
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class55.C
@@ -0,0 +1,15 @@ 
+// PR c++/102553
+// { dg-do compile { target c++20 } }
+
+struct s1{};
+template<int> inline constexpr s1 ch{};
+
+template<s1 f> struct s2{};
+template<s1 f> using alias1 = s2<f>;
+
+template<class T>
+void general(int n) {
+  alias1<ch<1>>{};
+}
+
+template void general<int>(int);