[clang] [Sema]Substitue parameter packs when deduced from function parameter (PR #79371)

Gábor Spaits via cfe-commits cfe-commits at lists.llvm.org
Thu Jan 25 11:38:01 PST 2024


https://github.com/spaits updated https://github.com/llvm/llvm-project/pull/79371

>From c00b8bd525b6acab45009188a7b9d1cb8c7eb30d Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Wed, 24 Jan 2024 21:21:26 +0100
Subject: [PATCH 01/12] [Sema]Substitue template parameter packs when deduced
 from function parameter

---
 clang/lib/Sema/SemaTemplateDeduction.cpp | 63 +++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index e9e7ab5bb6698a..46fa9eece3747a 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -730,6 +730,7 @@ class PackDeductionScope {
   void addPack(unsigned Index) {
     // Save the deduced template argument for the parameter pack expanded
     // by this pack expansion, then clear out the deduction.
+    DeducedFromEarlierParameter = !Deduced[Index].isNull();
     DeducedPack Pack(Index);
     Pack.Saved = Deduced[Index];
     Deduced[Index] = TemplateArgument();
@@ -858,6 +859,29 @@ class PackDeductionScope {
       Info.PendingDeducedPacks[Pack.Index] = Pack.Outer;
   }
 
+  std::optional<unsigned> getSavedPackSize(unsigned Index,
+                                           TemplateArgument Pattern) const {
+
+    SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+    S.collectUnexpandedParameterPacks(Pattern, Unexpanded);
+    if (Unexpanded.size() == 0 ||
+        Packs[0].Saved.getKind() != clang::TemplateArgument::Pack)
+      return {};
+    unsigned PackSize = Packs[0].Saved.pack_size();
+
+    if (std::all_of(Packs.begin() + 1, Packs.end(),
+                    [&PackSize](auto P) {
+                      return P.Saved.getKind() == TemplateArgument::Pack &&
+                             P.Saved.pack_size() == PackSize;
+                    }))
+      return PackSize;
+    return {};
+  }
+
+  /// Determine whether this pack has already been deduced from a previous
+  /// argument.
+  bool isDeducedFromEarlierParameter() const {return DeducedFromEarlierParameter;}
+
   /// Determine whether this pack has already been partially expanded into a
   /// sequence of (prior) function parameters / template arguments.
   bool isPartiallyExpanded() { return IsPartiallyExpanded; }
@@ -970,7 +994,6 @@ class PackDeductionScope {
         NewPack = Pack.DeferredDeduction;
         Result = checkDeducedTemplateArguments(S.Context, OldPack, NewPack);
       }
-
       NamedDecl *Param = TemplateParams->getParam(Pack.Index);
       if (Result.isNull()) {
         Info.Param = makeTemplateParameter(Param);
@@ -1003,9 +1026,12 @@ class PackDeductionScope {
   unsigned PackElements = 0;
   bool IsPartiallyExpanded = false;
   bool DeducePackIfNotAlreadyDeduced = false;
+  bool DeducedFromEarlierParameter = false;
+
   /// The number of expansions, if we have a fully-expanded pack in this scope.
   std::optional<unsigned> FixedNumExpansions;
 
+
   SmallVector<DeducedPack, 2> Packs;
 };
 
@@ -4371,6 +4397,41 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
           // corresponding argument is a list?
           PackScope.nextPackElement();
         }
+      } else if (!IsTrailingPack && !PackScope.isPartiallyExpanded() &&
+                 PackScope.isDeducedFromEarlierParameter() &&
+                 !isa<PackExpansionType>(ParamTypes[ParamIdx + 1])) {
+        // [temp.deduct.general#3]
+        // When all template arguments have been deduced
+        // or obtained from default template arguments, all uses of template
+        // parameters in the template parameter list of the template are
+        // replaced with the corresponding deduced or default argument values
+        //
+        // If we have a trailing parameter pack, that has been deduced perviously
+        // we substitute the pack here in a similar fashion as seen above with
+        // the trailing parameter packs. The main difference here is that, in
+        // this case we are not processing all of the remaining arguments. We
+        // are only process as many arguments as much we have in the already
+        // deduced parameter.
+        SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+        collectUnexpandedParameterPacks(ParamPattern, Unexpanded);
+        if (Unexpanded.size() == 0)
+          continue;
+
+        std::optional<unsigned> ArgPosAfterSubstitution =
+            PackScope.getSavedPackSize(getDepthAndIndex(Unexpanded[0]).second,
+                                       ParamPattern);
+        if (!ArgPosAfterSubstitution)
+          continue;
+
+        unsigned PackArgEnd = ArgIdx + *ArgPosAfterSubstitution;
+        for (; ArgIdx < PackArgEnd && ArgIdx < Args.size(); ArgIdx++) {
+          ParamTypesForArgChecking.push_back(ParamPattern);
+          if (auto Result = DeduceCallArgument(ParamPattern, ArgIdx,
+                                               /*ExplicitObjetArgument=*/false))
+            return Result;
+
+          PackScope.nextPackElement();
+        }
       }
     }
 

>From 1105354e001b41cac22836e0ac1df9eb8e3331cd Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Wed, 24 Jan 2024 22:33:02 +0100
Subject: [PATCH 02/12] Format changes

---
 clang/lib/Sema/SemaTemplateDeduction.cpp | 25 ++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 46fa9eece3747a..e682ca6dc0f7d4 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -869,18 +869,19 @@ class PackDeductionScope {
       return {};
     unsigned PackSize = Packs[0].Saved.pack_size();
 
-    if (std::all_of(Packs.begin() + 1, Packs.end(),
-                    [&PackSize](auto P) {
-                      return P.Saved.getKind() == TemplateArgument::Pack &&
-                             P.Saved.pack_size() == PackSize;
-                    }))
+    if (std::all_of(Packs.begin() + 1, Packs.end(), [&PackSize](auto P) {
+          return P.Saved.getKind() == TemplateArgument::Pack &&
+                 P.Saved.pack_size() == PackSize;
+        }))
       return PackSize;
     return {};
   }
 
   /// Determine whether this pack has already been deduced from a previous
   /// argument.
-  bool isDeducedFromEarlierParameter() const {return DeducedFromEarlierParameter;}
+  bool isDeducedFromEarlierParameter() const {
+    return DeducedFromEarlierParameter;
+  }
 
   /// Determine whether this pack has already been partially expanded into a
   /// sequence of (prior) function parameters / template arguments.
@@ -4406,12 +4407,12 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
         // parameters in the template parameter list of the template are
         // replaced with the corresponding deduced or default argument values
         //
-        // If we have a trailing parameter pack, that has been deduced perviously
-        // we substitute the pack here in a similar fashion as seen above with
-        // the trailing parameter packs. The main difference here is that, in
-        // this case we are not processing all of the remaining arguments. We
-        // are only process as many arguments as much we have in the already
-        // deduced parameter.
+        // If we have a trailing parameter pack, that has been deduced
+        // perviously we substitute the pack here in a similar fashion as seen
+        // above with the trailing parameter packs. The main difference here is
+        // that, in this case we are not processing all of the remaining
+        // arguments. We are only process as many arguments as much we have in
+        // the already deduced parameter.
         SmallVector<UnexpandedParameterPack, 2> Unexpanded;
         collectUnexpandedParameterPacks(ParamPattern, Unexpanded);
         if (Unexpanded.size() == 0)

>From fcc1f6a2910279e54355ddf44bb09528d02903ac Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 25 Jan 2024 08:22:29 +0100
Subject: [PATCH 03/12] Adjust test case and format

---
 clang/lib/Sema/SemaTemplateDeduction.cpp                       | 3 +--
 .../temp/temp.fct.spec/temp.deduct/temp.deduct.call/p1-0x.cpp  | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index e682ca6dc0f7d4..7df522b93324b7 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -995,6 +995,7 @@ class PackDeductionScope {
         NewPack = Pack.DeferredDeduction;
         Result = checkDeducedTemplateArguments(S.Context, OldPack, NewPack);
       }
+
       NamedDecl *Param = TemplateParams->getParam(Pack.Index);
       if (Result.isNull()) {
         Info.Param = makeTemplateParameter(Param);
@@ -1028,11 +1029,9 @@ class PackDeductionScope {
   bool IsPartiallyExpanded = false;
   bool DeducePackIfNotAlreadyDeduced = false;
   bool DeducedFromEarlierParameter = false;
-
   /// The number of expansions, if we have a fully-expanded pack in this scope.
   std::optional<unsigned> FixedNumExpansions;
 
-
   SmallVector<DeducedPack, 2> Packs;
 };
 
diff --git a/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p1-0x.cpp b/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p1-0x.cpp
index 1da599ed3c27aa..2ac66b5055ffd5 100644
--- a/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p1-0x.cpp
+++ b/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p1-0x.cpp
@@ -81,7 +81,7 @@ void test_pair_deduction(int *ip, float *fp, double *dp) {
 template<typename ...Types> struct tuple { };
 
 template<typename ...Types>
-void pack_not_at_end(tuple<Types...>, Types... values, int); // expected-note {{<int *, double *> vs. <>}}
+void pack_not_at_end(tuple<Types...>, Types... values, int); // expected-note {{<int *, double *> vs. <int, int>}}
 
 void test_pack_not_at_end(tuple<int*, double*> t2) {
   pack_not_at_end(t2, 0, 0, 0); // expected-error {{no match}}

>From 113df787dac108c8c83dc0c29c17a5d2f19bf801 Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 25 Jan 2024 08:36:02 +0100
Subject: [PATCH 04/12] Add tests

---
 clang/test/SemaTemplate/deduction.cpp | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/clang/test/SemaTemplate/deduction.cpp b/clang/test/SemaTemplate/deduction.cpp
index ad98de61d9e127..5f18471becc9e8 100644
--- a/clang/test/SemaTemplate/deduction.cpp
+++ b/clang/test/SemaTemplate/deduction.cpp
@@ -13,6 +13,17 @@ struct X0<int, A> {
   static const unsigned value = 1;
 };
 
+template<class T>
+struct type_identity {
+    using type = T;
+};
+
+template<class T>
+using type_identity_t = typename type_identity<T>::type;
+
+template <typename... T>
+struct args_tag {};
+
 template<int> struct X0i;
 template<long> struct X0l;
 int array_x0a[X0<long, X0l>::value == 0? 1 : -1];
@@ -431,6 +442,17 @@ namespace deduction_after_explicit_pack {
     i<int, int>(0, 1, 2, 3, 4, 5); // expected-error {{no match}}
   }
 
+  template <typename... T>
+  void bar(args_tag<T...>, type_identity_t<T>..., int mid, type_identity_t<T>...) {}
+  void call_bar() {
+    bar(args_tag<int, int>{}, 4, 8, 1001, 16, 23);
+  }
+  template <typename... Y, typename... T>
+  void foo(args_tag<Y...>, args_tag<T...>, type_identity_t<T>..., int mid, type_identity_t<T>...) {}
+  void call_foo() {
+    foo(args_tag<const int,const int, const int>{}, args_tag<int, int, int>{}, 4, 8, 9, 15, 16, 23, 1);
+  }
+
   // GCC alarmingly accepts this by deducing T={int} by matching the second
   // parameter against the first argument, then passing the first argument
   // through the first parameter.

>From edef1540aff1618cfe9dce8266e2e102db8066b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Spaits?=
 <48805437+spaits at users.noreply.github.com>
Date: Thu, 25 Jan 2024 11:05:42 +0100
Subject: [PATCH 05/12] Fix typo

Co-authored-by: cor3ntin <corentinjabot at gmail.com>
---
 clang/lib/Sema/SemaTemplateDeduction.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 7df522b93324b7..15c5a30371994d 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4407,7 +4407,7 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
         // replaced with the corresponding deduced or default argument values
         //
         // If we have a trailing parameter pack, that has been deduced
-        // perviously we substitute the pack here in a similar fashion as seen
+        // previously we substitute the pack here in a similar fashion as seen
         // above with the trailing parameter packs. The main difference here is
         // that, in this case we are not processing all of the remaining
         // arguments. We are only process as many arguments as much we have in

>From b437177f3853ef3da915e0e2b7b79caf3f3b01ca Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 25 Jan 2024 11:51:15 +0100
Subject: [PATCH 06/12] Fix typo #2

---
 clang/lib/Sema/SemaTemplateDeduction.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 15c5a30371994d..e3eed657e93f1d 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4407,7 +4407,7 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
         // replaced with the corresponding deduced or default argument values
         //
         // If we have a trailing parameter pack, that has been deduced
-        // previously we substitute the pack here in a similar fashion as seen
+        // previously we substitute the pack here in a similar fashion as
         // above with the trailing parameter packs. The main difference here is
         // that, in this case we are not processing all of the remaining
         // arguments. We are only process as many arguments as much we have in

>From 258e1f56c7ba062561ac4bfad407065b6ec0633a Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 25 Jan 2024 13:46:52 +0100
Subject: [PATCH 07/12] Substitue pack even when no separator between packs

---
 clang/lib/Sema/SemaTemplateDeduction.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index e3eed657e93f1d..0b04a66ce9d3f8 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4398,8 +4398,7 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
           PackScope.nextPackElement();
         }
       } else if (!IsTrailingPack && !PackScope.isPartiallyExpanded() &&
-                 PackScope.isDeducedFromEarlierParameter() &&
-                 !isa<PackExpansionType>(ParamTypes[ParamIdx + 1])) {
+                 PackScope.isDeducedFromEarlierParameter()) {
         // [temp.deduct.general#3]
         // When all template arguments have been deduced
         // or obtained from default template arguments, all uses of template

>From 55f83d003a479d3a57d8b42ea5ded5b0954d672d Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 25 Jan 2024 18:13:05 +0100
Subject: [PATCH 08/12] Use assertion where we know that pack is not empty

---
 clang/lib/Sema/SemaTemplateDeduction.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 0b04a66ce9d3f8..0194f2da75f01e 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4413,8 +4413,7 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
         // the already deduced parameter.
         SmallVector<UnexpandedParameterPack, 2> Unexpanded;
         collectUnexpandedParameterPacks(ParamPattern, Unexpanded);
-        if (Unexpanded.size() == 0)
-          continue;
+        assert(Unexpanded.size() != 0 && "We must have an unexpanded pack\n");
 
         std::optional<unsigned> ArgPosAfterSubstitution =
             PackScope.getSavedPackSize(getDepthAndIndex(Unexpanded[0]).second,

>From 2b72276883cd2e35e0c6286d4241c1db3e4c652a Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 25 Jan 2024 18:56:30 +0100
Subject: [PATCH 09/12] Simplify logic for pack size computing

---
 clang/lib/Sema/SemaTemplateDeduction.cpp | 16 ++++------------
 1 file changed, 4 insertions(+), 12 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 0194f2da75f01e..808b307729835e 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -859,12 +859,9 @@ class PackDeductionScope {
       Info.PendingDeducedPacks[Pack.Index] = Pack.Outer;
   }
 
-  std::optional<unsigned> getSavedPackSize(unsigned Index,
-                                           TemplateArgument Pattern) const {
-
-    SmallVector<UnexpandedParameterPack, 2> Unexpanded;
-    S.collectUnexpandedParameterPacks(Pattern, Unexpanded);
-    if (Unexpanded.size() == 0 ||
+  // Return the size of the saved packs if all of them has the same size.
+  std::optional<unsigned> getSavedPackSizeIfAllEqual() const {
+    if (Packs.size() == 0 ||
         Packs[0].Saved.getKind() != clang::TemplateArgument::Pack)
       return {};
     unsigned PackSize = Packs[0].Saved.pack_size();
@@ -4411,13 +4408,8 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
         // that, in this case we are not processing all of the remaining
         // arguments. We are only process as many arguments as much we have in
         // the already deduced parameter.
-        SmallVector<UnexpandedParameterPack, 2> Unexpanded;
-        collectUnexpandedParameterPacks(ParamPattern, Unexpanded);
-        assert(Unexpanded.size() != 0 && "We must have an unexpanded pack\n");
-
         std::optional<unsigned> ArgPosAfterSubstitution =
-            PackScope.getSavedPackSize(getDepthAndIndex(Unexpanded[0]).second,
-                                       ParamPattern);
+            PackScope.getSavedPackSizeIfAllEqual();
         if (!ArgPosAfterSubstitution)
           continue;
 

>From 91074e6c47c0b10dbcafa80e73cde315dab9e54d Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 25 Jan 2024 19:14:58 +0100
Subject: [PATCH 10/12] Add a test case for deduction without separator

---
 clang/test/SemaTemplate/deduction.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/clang/test/SemaTemplate/deduction.cpp b/clang/test/SemaTemplate/deduction.cpp
index 5f18471becc9e8..b7988648431c67 100644
--- a/clang/test/SemaTemplate/deduction.cpp
+++ b/clang/test/SemaTemplate/deduction.cpp
@@ -447,12 +447,18 @@ namespace deduction_after_explicit_pack {
   void call_bar() {
     bar(args_tag<int, int>{}, 4, 8, 1001, 16, 23);
   }
+
   template <typename... Y, typename... T>
   void foo(args_tag<Y...>, args_tag<T...>, type_identity_t<T>..., int mid, type_identity_t<T>...) {}
   void call_foo() {
     foo(args_tag<const int,const int, const int>{}, args_tag<int, int, int>{}, 4, 8, 9, 15, 16, 23, 1);
   }
 
+  template <typename... Y, typename... T> void baz(args_tag<T...>, T..., T...) {}
+  void call_baz() {
+    baz(args_tag<int, int>{}, 1, 2, 3, 4);
+  }
+
   // GCC alarmingly accepts this by deducing T={int} by matching the second
   // parameter against the first argument, then passing the first argument
   // through the first parameter.

>From bf1998b820e955ed025a60ef5ef031ce735a9597 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Spaits?=
 <48805437+spaits at users.noreply.github.com>
Date: Thu, 25 Jan 2024 20:16:33 +0100
Subject: [PATCH 11/12] Add const to lambda parameter

Co-authored-by: Erich Keane <ekeane at nvidia.com>
---
 clang/lib/Sema/SemaTemplateDeduction.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 808b307729835e..9f11c4614fbed6 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -866,7 +866,7 @@ class PackDeductionScope {
       return {};
     unsigned PackSize = Packs[0].Saved.pack_size();
 
-    if (std::all_of(Packs.begin() + 1, Packs.end(), [&PackSize](auto P) {
+    if (std::all_of(Packs.begin() + 1, Packs.end(), [&PackSize](const auto &P) {
           return P.Saved.getKind() == TemplateArgument::Pack &&
                  P.Saved.pack_size() == PackSize;
         }))

>From 1307230d7bfb67cc01976c634a3a0e2016f11792 Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 25 Jan 2024 20:37:42 +0100
Subject: [PATCH 12/12] Update release notes

---
 clang/docs/ReleaseNotes.rst | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 05c42f8485d4ee..4d5dc20e23dbfd 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -70,7 +70,9 @@ C++2c Feature Support
 
 Resolutions to C++ Defect Reports
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
+- Substitute template parameter pack, when it is not explicitly specified
+  in the template parameters, but is deduced from a previous argument.
+  (`#78449: <https://github.com/llvm/llvm-project/issues/78449>`_).
 C Language Changes
 ------------------
 



More information about the cfe-commits mailing list