PR42104: Support instantiations of lambdas that implicitly capture

packs.

Two changes:
 * Track odr-use via FunctionParmPackExprs to properly handle dependent
   odr-uses of packs in generic lambdas.
 * Do not instantiate implicit captures; instead, regenerate them by
   instantiating the body of the lambda. This is necessary to
   distinguish between cases where only one element of a pack is
   captured and cases where the entire pack is captured.

This reinstates r362358 (reverted in r362375) with a fix for an
uninitialized variable use in UpdateMarkingForLValueToRValue.

llvm-svn: 362531
This commit is contained in:
Richard Smith 2019-06-04 17:17:20 +00:00
parent f4302ad35e
commit 7bf8f6fa8a
9 changed files with 161 additions and 67 deletions

View File

@ -15,6 +15,7 @@
#define LLVM_CLANG_SEMA_SCOPEINFO_H #define LLVM_CLANG_SEMA_SCOPEINFO_H
#include "clang/AST/Expr.h" #include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/Type.h" #include "clang/AST/Type.h"
#include "clang/Basic/CapturedStmt.h" #include "clang/Basic/CapturedStmt.h"
#include "clang/Basic/LLVM.h" #include "clang/Basic/LLVM.h"
@ -913,7 +914,8 @@ public:
/// }; /// };
/// } /// }
void addPotentialCapture(Expr *VarExpr) { void addPotentialCapture(Expr *VarExpr) {
assert(isa<DeclRefExpr>(VarExpr) || isa<MemberExpr>(VarExpr)); assert(isa<DeclRefExpr>(VarExpr) || isa<MemberExpr>(VarExpr) ||
isa<FunctionParmPackExpr>(VarExpr));
PotentiallyCapturingExprs.push_back(VarExpr); PotentiallyCapturingExprs.push_back(VarExpr);
} }
@ -965,13 +967,15 @@ public:
/// building such a node. So we need a rule that anyone can implement and get /// building such a node. So we need a rule that anyone can implement and get
/// exactly the same result". /// exactly the same result".
void markVariableExprAsNonODRUsed(Expr *CapturingVarExpr) { void markVariableExprAsNonODRUsed(Expr *CapturingVarExpr) {
assert(isa<DeclRefExpr>(CapturingVarExpr) assert(isa<DeclRefExpr>(CapturingVarExpr) ||
|| isa<MemberExpr>(CapturingVarExpr)); isa<MemberExpr>(CapturingVarExpr) ||
isa<FunctionParmPackExpr>(CapturingVarExpr));
NonODRUsedCapturingExprs.insert(CapturingVarExpr); NonODRUsedCapturingExprs.insert(CapturingVarExpr);
} }
bool isVariableExprMarkedAsNonODRUsed(Expr *CapturingVarExpr) const { bool isVariableExprMarkedAsNonODRUsed(Expr *CapturingVarExpr) const {
assert(isa<DeclRefExpr>(CapturingVarExpr) assert(isa<DeclRefExpr>(CapturingVarExpr) ||
|| isa<MemberExpr>(CapturingVarExpr)); isa<MemberExpr>(CapturingVarExpr) ||
isa<FunctionParmPackExpr>(CapturingVarExpr));
return NonODRUsedCapturingExprs.count(CapturingVarExpr); return NonODRUsedCapturingExprs.count(CapturingVarExpr);
} }
void removePotentialCapture(Expr *E) { void removePotentialCapture(Expr *E) {
@ -993,9 +997,8 @@ public:
PotentialThisCaptureLocation.isValid(); PotentialThisCaptureLocation.isValid();
} }
// When passed the index, returns the VarDecl and Expr associated void visitPotentialCaptures(
// with the index. llvm::function_ref<void(VarDecl *, Expr *)> Callback) const;
void getPotentialVariableCapture(unsigned Idx, VarDecl *&VD, Expr *&E) const;
}; };
FunctionScopeInfo::WeakObjectProfileTy::WeakObjectProfileTy() FunctionScopeInfo::WeakObjectProfileTy::WeakObjectProfileTy()

View File

@ -4181,6 +4181,7 @@ public:
void MarkVariableReferenced(SourceLocation Loc, VarDecl *Var); void MarkVariableReferenced(SourceLocation Loc, VarDecl *Var);
void MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base = nullptr); void MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base = nullptr);
void MarkMemberReferenced(MemberExpr *E); void MarkMemberReferenced(MemberExpr *E);
void MarkFunctionParmPackReferenced(FunctionParmPackExpr *E);
void MarkCaptureUsedInEnclosingContext(VarDecl *Capture, SourceLocation Loc, void MarkCaptureUsedInEnclosingContext(VarDecl *Capture, SourceLocation Loc,
unsigned CapturingScopeIndex); unsigned CapturingScopeIndex);

View File

@ -229,20 +229,20 @@ bool CapturingScopeInfo::isVLATypeCaptured(const VariableArrayType *VAT) const {
return false; return false;
} }
void LambdaScopeInfo::getPotentialVariableCapture(unsigned Idx, VarDecl *&VD, void LambdaScopeInfo::visitPotentialCaptures(
Expr *&E) const { llvm::function_ref<void(VarDecl *, Expr *)> Callback) const {
assert(Idx < getNumPotentialVariableCaptures() && for (Expr *E : PotentiallyCapturingExprs) {
"Index of potential capture must be within 0 to less than the " if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
"number of captures!"); Callback(cast<VarDecl>(DRE->getFoundDecl()), E);
E = PotentiallyCapturingExprs[Idx]; } else if (auto *ME = dyn_cast<MemberExpr>(E)) {
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) Callback(cast<VarDecl>(ME->getMemberDecl()), E);
VD = dyn_cast<VarDecl>(DRE->getFoundDecl()); } else if (auto *FP = dyn_cast<FunctionParmPackExpr>(E)) {
else if (MemberExpr *ME = dyn_cast<MemberExpr>(E)) for (VarDecl *VD : *FP)
VD = dyn_cast<VarDecl>(ME->getMemberDecl()); Callback(VD, E);
else } else {
llvm_unreachable("Only DeclRefExprs or MemberExprs should be added for " llvm_unreachable("unexpected expression in potential captures list");
"potential captures"); }
assert(VD); }
} }
FunctionScopeInfo::~FunctionScopeInfo() { } FunctionScopeInfo::~FunctionScopeInfo() { }

View File

@ -14610,7 +14610,9 @@ namespace {
// context so never needs to be transformed. // context so never needs to be transformed.
// FIXME: Ideally we wouldn't transform the closure type either, and would // FIXME: Ideally we wouldn't transform the closure type either, and would
// just recreate the capture expressions and lambda expression. // just recreate the capture expressions and lambda expression.
StmtResult TransformLambdaBody(Stmt *Body) { return Body; } StmtResult TransformLambdaBody(LambdaExpr *E, Stmt *Body) {
return SkipLambdaBody(E, Body);
}
}; };
} }
@ -15054,7 +15056,7 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
/// *FunctionScopeIndexToStopAt on the FunctionScopeInfo stack. /// *FunctionScopeIndexToStopAt on the FunctionScopeInfo stack.
static void static void
MarkVarDeclODRUsed(VarDecl *Var, SourceLocation Loc, Sema &SemaRef, MarkVarDeclODRUsed(VarDecl *Var, SourceLocation Loc, Sema &SemaRef,
const unsigned *const FunctionScopeIndexToStopAt) { const unsigned *const FunctionScopeIndexToStopAt = nullptr) {
// Keep track of used but undefined variables. // Keep track of used but undefined variables.
// FIXME: We shouldn't suppress this warning for static data members. // FIXME: We shouldn't suppress this warning for static data members.
if (Var->hasDefinition(SemaRef.Context) == VarDecl::DeclarationOnly && if (Var->hasDefinition(SemaRef.Context) == VarDecl::DeclarationOnly &&
@ -15735,15 +15737,22 @@ void Sema::UpdateMarkingForLValueToRValue(Expr *E) {
// variable. // variable.
if (LambdaScopeInfo *LSI = getCurLambda()) { if (LambdaScopeInfo *LSI = getCurLambda()) {
Expr *SansParensExpr = E->IgnoreParens(); Expr *SansParensExpr = E->IgnoreParens();
VarDecl *Var = nullptr; VarDecl *Var;
ArrayRef<VarDecl *> Vars(&Var, &Var + 1);
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(SansParensExpr)) if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(SansParensExpr))
Var = dyn_cast<VarDecl>(DRE->getFoundDecl()); Var = dyn_cast<VarDecl>(DRE->getFoundDecl());
else if (MemberExpr *ME = dyn_cast<MemberExpr>(SansParensExpr)) else if (MemberExpr *ME = dyn_cast<MemberExpr>(SansParensExpr))
Var = dyn_cast<VarDecl>(ME->getMemberDecl()); Var = dyn_cast<VarDecl>(ME->getMemberDecl());
else if (auto *FPPE = dyn_cast<FunctionParmPackExpr>(SansParensExpr))
Vars = llvm::makeArrayRef(FPPE->begin(), FPPE->end());
else
Vars = None;
if (Var && IsVariableNonDependentAndAConstantExpression(Var, Context)) for (VarDecl *VD : Vars) {
if (VD && IsVariableNonDependentAndAConstantExpression(VD, Context))
LSI->markVariableExprAsNonODRUsed(SansParensExpr); LSI->markVariableExprAsNonODRUsed(SansParensExpr);
} }
}
} }
ExprResult Sema::ActOnConstantExpression(ExprResult Res) { ExprResult Sema::ActOnConstantExpression(ExprResult Res) {
@ -15767,20 +15776,18 @@ void Sema::CleanupVarDeclMarking() {
std::swap(LocalMaybeODRUseExprs, MaybeODRUseExprs); std::swap(LocalMaybeODRUseExprs, MaybeODRUseExprs);
for (Expr *E : LocalMaybeODRUseExprs) { for (Expr *E : LocalMaybeODRUseExprs) {
VarDecl *Var; if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
SourceLocation Loc; MarkVarDeclODRUsed(cast<VarDecl>(DRE->getDecl()),
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { DRE->getLocation(), *this);
Var = cast<VarDecl>(DRE->getDecl()); } else if (auto *ME = dyn_cast<MemberExpr>(E)) {
Loc = DRE->getLocation(); MarkVarDeclODRUsed(cast<VarDecl>(ME->getMemberDecl()), ME->getMemberLoc(),
} else if (MemberExpr *ME = dyn_cast<MemberExpr>(E)) { *this);
Var = cast<VarDecl>(ME->getMemberDecl()); } else if (auto *FP = dyn_cast<FunctionParmPackExpr>(E)) {
Loc = ME->getMemberLoc(); for (VarDecl *VD : *FP)
MarkVarDeclODRUsed(VD, FP->getParameterPackLocation(), *this);
} else { } else {
llvm_unreachable("Unexpected expression"); llvm_unreachable("Unexpected expression");
} }
MarkVarDeclODRUsed(Var, Loc, *this,
/*MaxFunctionScopeIndex Pointer*/ nullptr);
} }
assert(MaybeODRUseExprs.empty() && assert(MaybeODRUseExprs.empty() &&
@ -15789,7 +15796,8 @@ void Sema::CleanupVarDeclMarking() {
static void DoMarkVarDeclReferenced(Sema &SemaRef, SourceLocation Loc, static void DoMarkVarDeclReferenced(Sema &SemaRef, SourceLocation Loc,
VarDecl *Var, Expr *E) { VarDecl *Var, Expr *E) {
assert((!E || isa<DeclRefExpr>(E) || isa<MemberExpr>(E)) && assert((!E || isa<DeclRefExpr>(E) || isa<MemberExpr>(E) ||
isa<FunctionParmPackExpr>(E)) &&
"Invalid Expr argument to DoMarkVarDeclReferenced"); "Invalid Expr argument to DoMarkVarDeclReferenced");
Var->setReferenced(); Var->setReferenced();
@ -16022,6 +16030,12 @@ void Sema::MarkMemberReferenced(MemberExpr *E) {
MarkExprReferenced(*this, Loc, E->getMemberDecl(), E, MightBeOdrUse); MarkExprReferenced(*this, Loc, E->getMemberDecl(), E, MightBeOdrUse);
} }
/// Perform reference-marking and odr-use handling for a FunctionParmPackExpr.
void Sema::MarkFunctionParmPackReferenced(FunctionParmPackExpr *E) {
for (VarDecl *VD : *E)
MarkExprReferenced(*this, E->getParameterPackLocation(), VD, E, true);
}
/// Perform marking for a reference to an arbitrary declaration. It /// Perform marking for a reference to an arbitrary declaration. It
/// marks the declaration referenced, and performs odr-use checking for /// marks the declaration referenced, and performs odr-use checking for
/// functions and variables. This method should not be used when building a /// functions and variables. This method should not be used when building a

View File

@ -7427,12 +7427,7 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
// All the potentially captureable variables in the current nested // All the potentially captureable variables in the current nested
// lambda (within a generic outer lambda), must be captured by an // lambda (within a generic outer lambda), must be captured by an
// outer lambda that is enclosed within a non-dependent context. // outer lambda that is enclosed within a non-dependent context.
const unsigned NumPotentialCaptures = CurrentLSI->visitPotentialCaptures([&] (VarDecl *Var, Expr *VarExpr) {
CurrentLSI->getNumPotentialVariableCaptures();
for (unsigned I = 0; I != NumPotentialCaptures; ++I) {
Expr *VarExpr = nullptr;
VarDecl *Var = nullptr;
CurrentLSI->getPotentialVariableCapture(I, Var, VarExpr);
// If the variable is clearly identified as non-odr-used and the full // If the variable is clearly identified as non-odr-used and the full
// expression is not instantiation dependent, only then do we not // expression is not instantiation dependent, only then do we not
// need to check enclosing lambda's for speculative captures. // need to check enclosing lambda's for speculative captures.
@ -7446,7 +7441,7 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
// } // }
if (CurrentLSI->isVariableExprMarkedAsNonODRUsed(VarExpr) && if (CurrentLSI->isVariableExprMarkedAsNonODRUsed(VarExpr) &&
!IsFullExprInstantiationDependent) !IsFullExprInstantiationDependent)
continue; return;
// If we have a capture-capable lambda for the variable, go ahead and // If we have a capture-capable lambda for the variable, go ahead and
// capture the variable in that lambda (and all its enclosing lambdas). // capture the variable in that lambda (and all its enclosing lambdas).
@ -7478,7 +7473,7 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
DeclRefType, nullptr); DeclRefType, nullptr);
} }
} }
} });
// Check if 'this' needs to be captured. // Check if 'this' needs to be captured.
if (CurrentLSI->hasPotentialThisCapture()) { if (CurrentLSI->hasPotentialThisCapture()) {

View File

@ -1368,9 +1368,11 @@ TemplateInstantiator::TransformFunctionParmPackExpr(FunctionParmPackExpr *E) {
Vars.push_back(D); Vars.push_back(D);
} }
return FunctionParmPackExpr::Create(getSema().Context, T, auto *PackExpr =
E->getParameterPack(), FunctionParmPackExpr::Create(getSema().Context, T, E->getParameterPack(),
E->getParameterPackLocation(), Vars); E->getParameterPackLocation(), Vars);
getSema().MarkFunctionParmPackReferenced(PackExpr);
return PackExpr;
} }
ExprResult ExprResult
@ -1389,8 +1391,10 @@ TemplateInstantiator::TransformFunctionParmPackRefExpr(DeclRefExpr *E,
QualType T = TransformType(E->getType()); QualType T = TransformType(E->getType());
if (T.isNull()) if (T.isNull())
return ExprError(); return ExprError();
return FunctionParmPackExpr::Create(getSema().Context, T, PD, auto *PackExpr = FunctionParmPackExpr::Create(getSema().Context, T, PD,
E->getExprLoc(), *Pack); E->getExprLoc(), *Pack);
getSema().MarkFunctionParmPackReferenced(PackExpr);
return PackExpr;
} }
TransformedDecl = (*Pack)[getSema().ArgumentPackSubstitutionIndex]; TransformedDecl = (*Pack)[getSema().ArgumentPackSubstitutionIndex];

View File

@ -660,7 +660,10 @@ public:
bool ExpectParameterPack); bool ExpectParameterPack);
/// Transform the body of a lambda-expression. /// Transform the body of a lambda-expression.
StmtResult TransformLambdaBody(Stmt *Body); StmtResult TransformLambdaBody(LambdaExpr *E, Stmt *Body);
/// Alternative implementation of TransformLambdaBody that skips transforming
/// the body.
StmtResult SkipLambdaBody(LambdaExpr *E, Stmt *Body);
QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL); QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL);
@ -11370,16 +11373,13 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
bool Invalid = false; bool Invalid = false;
// Transform captures. // Transform captures.
bool FinishedExplicitCaptures = false;
for (LambdaExpr::capture_iterator C = E->capture_begin(), for (LambdaExpr::capture_iterator C = E->capture_begin(),
CEnd = E->capture_end(); CEnd = E->capture_end();
C != CEnd; ++C) { C != CEnd; ++C) {
// When we hit the first implicit capture, tell Sema that we've finished // When we hit the first implicit capture, tell Sema that we've finished
// the list of explicit captures. // the list of explicit captures.
if (!FinishedExplicitCaptures && C->isImplicit()) { if (C->isImplicit())
getSema().finishLambdaExplicitCaptures(LSI); break;
FinishedExplicitCaptures = true;
}
// Capturing 'this' is trivial. // Capturing 'this' is trivial.
if (C->capturesThis()) { if (C->capturesThis()) {
@ -11488,17 +11488,16 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
getSema().tryCaptureVariable(CapturedVar, C->getLocation(), Kind, getSema().tryCaptureVariable(CapturedVar, C->getLocation(), Kind,
EllipsisLoc); EllipsisLoc);
} }
if (!FinishedExplicitCaptures)
getSema().finishLambdaExplicitCaptures(LSI); getSema().finishLambdaExplicitCaptures(LSI);
// Enter a new evaluation context to insulate the lambda from any // FIXME: Sema's lambda-building mechanism expects us to push an expression
// cleanups from the enclosing full-expression. // evaluation context even if we're not transforming the function body.
getSema().PushExpressionEvaluationContext( getSema().PushExpressionEvaluationContext(
Sema::ExpressionEvaluationContext::PotentiallyEvaluated); Sema::ExpressionEvaluationContext::PotentiallyEvaluated);
// Instantiate the body of the lambda expression. // Instantiate the body of the lambda expression.
StmtResult Body = StmtResult Body =
Invalid ? StmtError() : getDerived().TransformLambdaBody(E->getBody()); Invalid ? StmtError() : getDerived().TransformLambdaBody(E, E->getBody());
// ActOnLambda* will pop the function scope for us. // ActOnLambda* will pop the function scope for us.
FuncScopeCleanup.disable(); FuncScopeCleanup.disable();
@ -11524,10 +11523,50 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
template<typename Derived> template<typename Derived>
StmtResult StmtResult
TreeTransform<Derived>::TransformLambdaBody(Stmt *S) { TreeTransform<Derived>::TransformLambdaBody(LambdaExpr *E, Stmt *S) {
return TransformStmt(S); return TransformStmt(S);
} }
template<typename Derived>
StmtResult
TreeTransform<Derived>::SkipLambdaBody(LambdaExpr *E, Stmt *S) {
// Transform captures.
for (LambdaExpr::capture_iterator C = E->capture_begin(),
CEnd = E->capture_end();
C != CEnd; ++C) {
// When we hit the first implicit capture, tell Sema that we've finished
// the list of explicit captures.
if (!C->isImplicit())
continue;
// Capturing 'this' is trivial.
if (C->capturesThis()) {
getSema().CheckCXXThisCapture(C->getLocation(), C->isExplicit(),
/*BuildAndDiagnose*/ true, nullptr,
C->getCaptureKind() == LCK_StarThis);
continue;
}
// Captured expression will be recaptured during captured variables
// rebuilding.
if (C->capturesVLAType())
continue;
assert(C->capturesVariable() && "unexpected kind of lambda capture");
assert(!E->isInitCapture(C) && "implicit init-capture?");
// Transform the captured variable.
VarDecl *CapturedVar = cast_or_null<VarDecl>(
getDerived().TransformDecl(C->getLocation(), C->getCapturedVar()));
if (!CapturedVar || CapturedVar->isInvalidDecl())
return StmtError();
// Capture the transformed variable.
getSema().tryCaptureVariable(CapturedVar, C->getLocation());
}
return S;
}
template<typename Derived> template<typename Derived>
ExprResult ExprResult
TreeTransform<Derived>::TransformCXXUnresolvedConstructExpr( TreeTransform<Derived>::TransformCXXUnresolvedConstructExpr(

View File

@ -1,4 +1,5 @@
// RUN: %clang_cc1 -std=c++1y -verify -fsyntax-only -fblocks -emit-llvm-only %s // RUN: %clang_cc1 -std=c++1y -verify -fsyntax-only -fblocks -emit-llvm-only %s
// RUN: %clang_cc1 -std=c++2a -verify -fsyntax-only -fblocks -emit-llvm-only %s
// DONTRUNYET: %clang_cc1 -std=c++1y -verify -fsyntax-only -fblocks -fdelayed-template-parsing %s -DDELAYED_TEMPLATE_PARSING // DONTRUNYET: %clang_cc1 -std=c++1y -verify -fsyntax-only -fblocks -fdelayed-template-parsing %s -DDELAYED_TEMPLATE_PARSING
// DONTRUNYET: %clang_cc1 -std=c++1y -verify -fsyntax-only -fblocks -fms-extensions %s -DMS_EXTENSIONS // DONTRUNYET: %clang_cc1 -std=c++1y -verify -fsyntax-only -fblocks -fms-extensions %s -DMS_EXTENSIONS
// DONTRUNYET: %clang_cc1 -std=c++1y -verify -fsyntax-only -fblocks -fdelayed-template-parsing -fms-extensions %s -DMS_EXTENSIONS -DDELAYED_TEMPLATE_PARSING // DONTRUNYET: %clang_cc1 -std=c++1y -verify -fsyntax-only -fblocks -fdelayed-template-parsing -fms-extensions %s -DMS_EXTENSIONS -DDELAYED_TEMPLATE_PARSING
@ -176,7 +177,13 @@ void doit() {
sample::X cx{5}; sample::X cx{5};
auto L = [=](auto a) { auto L = [=](auto a) {
const int z = 3; const int z = 3;
// FIXME: The warning below is correct but for some reason doesn't show
// up in C++17 mode.
return [&,a](auto b) { return [&,a](auto b) {
#if __cplusplus > 201702L
// expected-warning@-2 {{address of stack memory associated with local variable 'z' returned}}
// expected-note@#call {{in instantiation of}}
#endif
const int y = 5; const int y = 5;
return [=](auto c) { return [=](auto c) {
int d[sizeof(a) == sizeof(c) || sizeof(c) == sizeof(b) ? 2 : 1]; int d[sizeof(a) == sizeof(c) || sizeof(c) == sizeof(b) ? 2 : 1];
@ -189,7 +196,7 @@ void doit() {
}; };
}; };
}; };
auto M = L(3)(3.5); auto M = L(3)(3.5); // #call
M(3.14); M(3.14);
} }
} }
@ -1519,6 +1526,20 @@ void test() {
} // end ns5 } // end ns5
} // end PR34266 } // end PR34266
namespace capture_pack {
#if __cplusplus >= 201702L
constexpr
#endif
auto v =
[](auto ...a) {
[&](auto ...b) {
((a = b), ...); // expected-warning 0-1{{extension}}
}(100, 20, 3);
return (a + ...); // expected-warning 0-1{{extension}}
}(400, 50, 6);
#if __cplusplus >= 201702L
static_assert(v == 123);
#endif
}

View File

@ -0,0 +1,17 @@
// RUN: %clang_cc1 -std=c++2a -verify %s
// expected-no-diagnostics
template<typename ...T, typename ...Lambda> void check_sizes(Lambda ...L) {
static_assert(((sizeof(T) == sizeof(Lambda)) && ...));
}
template<typename ...T> void f(T ...v) {
// Pack expansion of lambdas: each lambda captures only one pack element.
check_sizes<T...>([=] { (void)&v; } ...);
// Pack expansion inside lambda: captures all pack elements.
auto l = [=] { ((void)&v, ...); };
static_assert(sizeof(l) >= (sizeof(T) + ...));
}
template void f(int, char, double);