[clang] [Sema] Preserve ContainsUnexpandedParameterPack in TransformLambdaExpr (PR #86265)

Younan Zhang via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 26 08:20:51 PDT 2024


================
@@ -1394,7 +1395,22 @@ namespace {
                                  SourceRange PatternRange,
                                  ArrayRef<UnexpandedParameterPack> Unexpanded,
                                  bool &ShouldExpand, bool &RetainExpansion,
-                                 std::optional<unsigned> &NumExpansions) {
+                                 std::optional<unsigned> &NumExpansions,
+                                 bool ForConstraints = false) {
+      if (ForConstraints) {
+        LambdaScopeInfo *LSI = getSema().getCurLambda();
+        if (LSI) {
+          MultiLevelTemplateArgumentList MLTAL =
+              getSema().getTemplateInstantiationArgs(
+                  LSI->CallOperator, /*DC=*/nullptr, /*Final=*/false,
+                  /*Innermost=*/std::nullopt, /*RelativeToPrimary=*/true,
+                  /*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true);
+          return getSema().CheckParameterPacksForExpansion(
+              EllipsisLoc, PatternRange, Unexpanded, MLTAL, ShouldExpand,
+              RetainExpansion, NumExpansions);
+        }
----------------
zyn0217 wrote:

This is the most convoluted part in this patch. Basically, this targets for a situation where unexpanded packs appear in a trailing constraint. On the basis of our evaluation strategy, we need the complete template arguments, (which are typically relative to the primary template, as opposed to other instantiation algorithm where we usually need the template arguments relative to the parent specializations.) whereas we don't always have them at the time expanding a CXXFoldExpr.

For example, suppose we have
```cpp
template <class = void> void foo() {
 []<class... Is>() {
    ([]()
       requires (!C<Is>)
     {}(),
     ...);
  }.template operator()<char, int, float>();
}
``` 

We would only have one level template arguments, which is `<char, int, float>` when the inner fold expression gets expanded. Since we always preserve the constraint expressions until we evaluate them, we would thereafter have the inner lambda being unexpanded, which is wrong because, later we need to evaluate an expanded constraint when forming CallExprs.

So, one way is to teach the transformation to handle the constraints separately:
If we have only a constraint that contains unexpanded packs, we need to *expand* the expression because we don't actually need to use the constraint immediately. (in other words, the untransformed constraint shouldn't impact the expansion)

And vice versa: if we have combined constraints and other unexpanded packs, we just do the usual: we could hold off on the expansion until we hit the point where only unexpanded constraints are left, which boils down to the above case.

https://github.com/llvm/llvm-project/pull/86265


More information about the cfe-commits mailing list