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

Gábor Spaits via cfe-commits cfe-commits at lists.llvm.org
Wed Jan 24 13:23:41 PST 2024


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

This pull request would solve https://github.com/llvm/llvm-project/issues/78449 .
There is also a discussion about this on stackoverflow: https://stackoverflow.com/questions/77832658/stdtype-identity-to-support-several-variadic-argument-lists .

The following program is well formed:
```cpp
#include <type_traits>

template <typename... T>
struct args_tag
{
    using type = std::common_type_t<T...>;
};

template <typename... T>
void bar(args_tag<T...>, std::type_identity_t<T>..., int, std::type_identity_t<T>...) {}

// example
int main() {
    bar(args_tag<int, int>{}, 4, 8, 15, 16, 23);
}
```

but Clang rejects it, while GCC and MSVC doesn't. The reason for this is that, in `Sema::DeduceTemplateArguments` we are not prepared for this case. 

The logic that handles substitution when we have explicit template arguments does not work here, since the types of the pack are not pushed to `ParamTypes` before the loop starts that does the deduction.
The other "candidate" that would may handle this case would be the loop that does the deduction for trailing packs, but we are not dealing with trailing packs here.

The solution proposed in this PR works similar to the trailing pack deduction. The main difference here is the end of the deduction cycle.
When a non-trailing template pack argument is found, whose type is not explicitly specified and the next type is not a pack type, the length of the previously deduced pack is retrieved (let that length be `s`). After that the next `s` arguments are processed in the same way as in the case of non trailing packs.

There is another possible approach that would be less efficient. In the loop when we get to an element of `ParamTypes` that is a pack and could be substituted because the type is deduced from a previous argument, then `s` number of arg types would be inserted before the current element of `ParamTypes` type. Then we would "cancel" the processing of the current element, first process the previously inserted elements and the after that re-process the current element.
Basically we would do what `SubstituteExplicitTemplateArguments` does but during deduction.

>From d4ca5c2fcb87f424be23efc4513df491403c3811 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] [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 e9e7ab5bb6698a0..46fa9eece3747a2 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();
+        }
       }
     }
 



More information about the cfe-commits mailing list