diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4850,7 +4850,8 @@ void MarkCaptureUsedInEnclosingContext(VarDecl *Capture, SourceLocation Loc, unsigned CapturingScopeIndex); - ExprResult CheckLValueToRValueConversionOperand(Expr *E); + ExprResult CheckLValueToRValueConversionOperand(Expr *E, + bool DiscardedValue = false); void CleanupVarDeclMarking(); enum TryCaptureKind { @@ -6268,7 +6269,8 @@ Expr, Expr ? Expr->getExprLoc() : SourceLocation(), DiscardedValue); } ExprResult ActOnFinishFullExpr(Expr *Expr, SourceLocation CC, - bool DiscardedValue, bool IsConstexpr = false); + bool DiscardedValue, bool IsConstexpr = false, + const QualType DestinationType = QualType()); StmtResult ActOnFinishFullStmt(Stmt *Stmt); // Marks SS invalid if it represents an incomplete type. diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -12200,9 +12200,9 @@ // struct T { S a, b; } t = { Temp(), Temp() } // // we should destroy the first Temp before constructing the second. - ExprResult Result = - ActOnFinishFullExpr(Init, VDecl->getLocation(), - /*DiscardedValue*/ false, VDecl->isConstexpr()); + ExprResult Result = ActOnFinishFullExpr( + Init, VDecl->getLocation(), + /*DiscardedValue*/ false, VDecl->isConstexpr(), VDecl->getType()); if (Result.isInvalid()) { VDecl->setInvalidDecl(); return; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -3984,7 +3984,8 @@ // C++11 [class.base.init]p7: // The initialization of each base and member constitutes a // full-expression. - Init = ActOnFinishFullExpr(Init.get(), InitLoc, /*DiscardedValue*/ false); + Init = ActOnFinishFullExpr(Init.get(), InitLoc, /*DiscardedValue*/ false, + /*IsConstexpr*/ false, FD->getType()); if (Init.isInvalid()) { FD->setInvalidDecl(); return; @@ -4348,7 +4349,8 @@ // The initialization of each base and member constitutes a // full-expression. MemberInit = ActOnFinishFullExpr(MemberInit.get(), InitRange.getBegin(), - /*DiscardedValue*/ false); + /*DiscardedValue*/ false, + /*isConstexpr*/ false, Member->getType()); if (MemberInit.isInvalid()) return true; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -17929,7 +17929,8 @@ return ExprEmpty(); } -ExprResult Sema::CheckLValueToRValueConversionOperand(Expr *E) { +ExprResult +Sema::CheckLValueToRValueConversionOperand(Expr *E, const bool DiscardedValue) { // Check whether the operand is or contains an object of non-trivial C union // type. if (E->getType().isVolatileQualified() && @@ -17942,7 +17943,8 @@ // C++2a [basic.def.odr]p4: // [...] an expression of non-volatile-qualified non-class type to which // the lvalue-to-rvalue conversion is applied [...] - if (E->getType().isVolatileQualified() || E->getType()->getAs()) + if (!DiscardedValue && + (E->getType().isVolatileQualified() || E->getType()->getAs())) return E; ExprResult Result = diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -23,6 +23,7 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtVisitor.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/AlignedAllocation.h" #include "clang/Basic/PartialDiagnostic.h" @@ -7702,6 +7703,73 @@ return BuildCXXNoexceptExpr(KeyLoc, Operand, RParen); } +// This follows the recursive algorithm specified in [basic.def.odr] to collect +// all the potential results of an expression into 'Results', which can be +// useful when determining odr-uses incurred by the expression. +static void extractPotentialResults(Sema &SemaRef, const Expr *TheExpr, + SmallVectorImpl &Results) { + + struct OdrUsePotentialResultsVisitor + : ConstStmtVisitor { + typedef ConstStmtVisitor Inherited; + + Sema &SemaRef; + SmallVectorImpl &Results; + + // When visiting a node that represents a subscript-expression involving a + // non-static array data member, we might need to traverse through an + // ArrayToPointer conversion. For e.g. + // constexpr struct X { int arr[] = { 1, 2, 3 }; } x; + // 'x.arr[0]' is ok, but not 'x.arr'. + bool VisitingArraySubscriptBaseExpr = false; + + OdrUsePotentialResultsVisitor(Sema &S, + SmallVectorImpl &Results) + : Inherited(), SemaRef(S), Results(Results) {} + + void run(const Expr *E) { Visit(E); } + void VisitDeclRefExpr(const DeclRefExpr *E) { Results.push_back(E); } + void VisitParenExpr(const ParenExpr *E) { Visit(E->getSubExpr()); } + void VisitBinComma(const BinaryOperator *E) { Visit(E->getRHS()); } + void VisitConditionalOperator(const ConditionalOperator *E) { + Visit(E->getLHS()); + Visit(E->getRHS()); + } + + void VisitMemberExpr(const MemberExpr *E) { + // Note, static data member accesses that were spelled x.s are represented + // as MemberExpr's while X::s as DeclRefExpr's. + const VarDecl *StaticDataMember = dyn_cast(E->getMemberDecl()); + if (StaticDataMember) { + Results.push_back(E); + } else { + // reference to a non-static data member. + Visit(E->getBase()); + } + } + + void VisitArraySubscriptExpr(const ArraySubscriptExpr *E) { + VisitingArraySubscriptBaseExpr = true; + Visit(E->getBase()); + VisitingArraySubscriptBaseExpr = false; + } + void VisitImplicitCastExpr(const ImplicitCastExpr *E) { + if (!VisitingArraySubscriptBaseExpr) return; + switch (E->getCastKind()) { + case CK_ArrayToPointerDecay: + Visit(E->getSubExpr()); + break; + default: + break; + + } + } + void VisitBinPtrMemD(const BinaryOperator *E) { Visit(E->getLHS()); } + void VisitBinPtrMemI(const BinaryOperator *E) { Visit(E->getLHS()); } + }; + OdrUsePotentialResultsVisitor(SemaRef, Results).run(TheExpr); +} + /// Perform the conversions required for an expression used in a /// context that ignores the result. ExprResult Sema::IgnoredValueConversions(Expr *E) { @@ -7740,6 +7808,9 @@ // Per C++2a [expr.ass]p5, a volatile assignment is not deprecated if // it occurs as a discarded-value expression. CheckUnusedVolatileAssignment(E); + auto ER = + CheckLValueToRValueConversionOperand(E, /*DiscardedValue=*/true); + E = ER.get(); } // C++1z: @@ -7796,23 +7867,18 @@ // delete, lambda-expr, dynamic-cast, reinterpret-cast etc... static inline bool VariableCanNeverBeAConstantExpression(VarDecl *Var, ASTContext &Context) { - if (isa(Var)) return true; + if (isa(Var) || Var->isWeak()) + return true; const VarDecl *DefVD = nullptr; - - // If there is no initializer - this can not be a constant expression. - if (!Var->getAnyInitializer(DefVD)) return true; - assert(DefVD); - if (DefVD->isWeak()) return false; - EvaluatedStmt *Eval = DefVD->ensureEvaluatedStmt(); - - Expr *Init = cast(Eval->Value); - - if (Var->getType()->isDependentType() || Init->isValueDependent()) { - // FIXME: Teach the constant evaluator to deal with the non-dependent parts - // of value-dependent expressions, and use it here to determine whether the - // initializer is a potential constant expression. - return false; + if (const Expr *Init = Var->getAnyInitializer(DefVD)) { + // FIXME: Perhaps, teach the constant evaluator to deal with the + // non-dependent parts of value-dependent expressions, and use it here to + // determine whether the initializer is a potential constant expression. + if (Init->isValueDependent()) + return false; } + if (Var->getType()->isDependentType()) + return false; return !Var->isUsableInConstantExpressions(Context); } @@ -7825,10 +7891,85 @@ /// need to be captured. static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures( - Expr *const FE, LambdaScopeInfo *const CurrentLSI, Sema &S) { + Expr *const FE, LambdaScopeInfo *const CurrentLSI, + const QualType DestinationType, Sema &S) { assert(!S.isUnevaluatedContext()); assert(S.CurContext->isDependentContext()); + + const bool IsNonCaptureableLambda = + /*CurrentLSI->Captures.empty() &&*/ + CurrentLSI->ImpCaptureStyle == LambdaScopeInfo::ImpCap_None; + + // TODO: + // Should we just disregard any potential captures in a discarded context in a + // lambda that can not capture it. In C++17, in a lambda with a default + // capture, we do capture such local entities. + + // [](auto a) { + // if constexpr (false) { + // const int &i = I; <-- this is not diagnosed, should it be? + // } + // }; + // i.e. should we do something along these lines: + // if (IsNonCaptureableLambda && S.isDiscardedStatement()) { + // CurrentLSI->clearPotentialCaptures(); + // return; + // } + + // If DestinationType is valid, we must be initializing something (return + // value, variable, data member, base class etc.) so we will need to inquire + // whether we have enough information to determine odr-use or not. + + // If we are either dependent on a temaplate parameter, or are a deduced + // return type that is not a reference type (i.e. 'auto', decltype(auto) + // but not 'auto&&'), remove any attempts at potentially capturing the results + // of such expressions - since instantiation might introduce lvalue-to-rvalue + // conversions and avert odr-use. We make an exception for deduced reference + // types since they cannot result in any lvalue-to-rvalue conversions + // (right?). + + const bool IsInitializingAnEntityWithDependentType = + !DestinationType.isNull() && + DestinationType->isDependentType(); + + + const AutoType *DestinationAutoType = + DestinationType.isNull() + ? nullptr + : DestinationType->getContainedAutoType(); + + const bool IsInitializingUndeducedAutoTypeWithinDependentCtx = + IsInitializingAnEntityWithDependentType && + DestinationAutoType; + + // We want to eagerly diagnose : auto &t = I; <- even if I is dependent + const bool IsReferenceRelatedAutoTypeDeduction = + IsInitializingUndeducedAutoTypeWithinDependentCtx && + DestinationType->isReferenceType(); + + const bool IsDecltypeAutoDeduction = + DestinationAutoType && DestinationAutoType->isDecltypeAuto(); + + // Extract the potential results ([basic.def.odr]) of this full-expression so + // that, once we determine that this full-expression might yet be a candidate + // for an lvalue-to-rvalue conversion in some instantiation, we can query this + // set to avoid any eager diagnostics related to such references. + + SmallVector PotentialResults; + extractPotentialResults(S, FE, PotentialResults); + + // If the full-expression is instantiation dependent, we try and proceed + // conservatively (i.e we do not interrogate subexpressions for + // non-dependence and analyze them for definite non-odr-use) and prohibit + // any diagnostics until instantiation time (when an lvalue-to-rvalue + // conversion might obviate capture), unless we can determine that the + // use of any sub-expressions can never be a constant expression (such as use + // of an eclosing parameter). + // For e.g. when processing the full-expression 't' in the return stmt below: + // template void f(const T t) { [] { return t; }; }; + const bool IsFullExprInstantiationDependent = FE->isInstantiationDependent(); + #ifndef NDEBUG DeclContext *DC = S.CurContext; while (DC && isa(DC)) @@ -7838,11 +7979,12 @@ "The current call operator must be synchronized with Sema's CurContext"); #endif // NDEBUG - const bool IsFullExprInstantiationDependent = FE->isInstantiationDependent(); + // All the potentially captureable variables in a current captureable nested + // lambda (within a generic outer lambda), must be captured by an outer lambda + // that is enclosed within a non-dependent context. If the lambda is not + // captureable then only diagnose those references that we determine require + // odr-use. - // All the potentially captureable variables in the current nested - // lambda (within a generic outer lambda), must be captured by an - // outer lambda that is enclosed within a non-dependent context. CurrentLSI->visitPotentialCaptures([&] (VarDecl *Var, Expr *VarExpr) { // If the variable is clearly identified as non-odr-used and the full // expression is not instantiation dependent, only then do we not @@ -7868,7 +8010,40 @@ Index.getValue()); const bool IsVarNeverAConstantExpression = VariableCanNeverBeAConstantExpression(Var, S.Context); - if (!IsFullExprInstantiationDependent || IsVarNeverAConstantExpression) { + + if (!IsFullExprInstantiationDependent && !IsVarNeverAConstantExpression && + IsNonCaptureableLambda && IsInitializingAnEntityWithDependentType && + !IsReferenceRelatedAutoTypeDeduction) { + + // If this VarExpr is a potential result of the full expression, and is + // initializing an entity of dependent type that is not an 'auto&'-like + // deduction, lets not capture this entity, since instantiation might + // introduce an lvalue-to-rvalue during initialization of the destination + // entity and obviate odr-use. + if (std::find(PotentialResults.begin(), PotentialResults.end(), + VarExpr) != PotentialResults.end()) + return; + + + + + } + + // Try and capture if we know any of the following: + // 1) the expression is not instantiation dependent (the cases where + // initialization to a dependent destination type might introduce an + // lvalue-to-rvalue conversion, have been addressed above.) + // 2) the VarDecl can never be a constant expression. + // 3) we are initializing to a reference-related type (via auto) + // 4) we are initializing to the declaration type of the VarDecl, and + // the variable is a reference. + + // (Note: constexpr references never need to be captured, but const + // references referring to temporaries need to be). + + if (!IsFullExprInstantiationDependent || IsVarNeverAConstantExpression || + IsReferenceRelatedAutoTypeDeduction || + (IsDecltypeAutoDeduction && Var->getType()->isReferenceType())) { // This full expression is not instantiation dependent or the variable // can not be used in a constant expression - which means // this variable must be odr-used here, so diagnose a @@ -8333,8 +8508,8 @@ } ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC, - bool DiscardedValue, - bool IsConstexpr) { + bool DiscardedValue, bool IsConstexpr, + const QualType DestinationType) { ExprResult FullExpr = FE; if (!FullExpr.get()) @@ -8422,8 +8597,8 @@ const bool IsInLambdaDeclContext = isLambdaCallOperator(DC); if (IsInLambdaDeclContext && CurrentLSI && CurrentLSI->hasPotentialCaptures() && !FullExpr.isInvalid()) - CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(FE, CurrentLSI, - *this); + CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures( + FE, CurrentLSI, DestinationType, *this); return MaybeCreateExprWithCleanups(FullExpr); } diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3317,7 +3317,8 @@ (HasDeducedReturnType || CurCap->HasImplicitReturnType)) { if (RetValExp) { ExprResult ER = - ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false); + ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false, + /*isConstexpr*/ false, FnRetType); if (ER.isInvalid()) return StmtError(); RetValExp = ER.get(); @@ -3446,7 +3447,8 @@ if (RetValExp) { ExprResult ER = - ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false); + ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false, + /*isConstexpr*/ false, FnRetType); if (ER.isInvalid()) return StmtError(); RetValExp = ER.get(); @@ -3695,7 +3697,8 @@ FnRetType->getContainedAutoType()) { if (RetValExp) { ExprResult ER = - ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false); + ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false, + /*isConstexpr*/ false, FnRetType); if (ER.isInvalid()) return StmtError(); RetValExp = ER.get(); @@ -3787,7 +3790,8 @@ if (RetValExp) { ExprResult ER = - ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false); + ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false, + /*isConstexpr*/ false, FnRetType); if (ER.isInvalid()) return StmtError(); RetValExp = ER.get(); @@ -3869,7 +3873,8 @@ if (RetValExp) { ExprResult ER = - ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false); + ActOnFinishFullExpr(RetValExp, ReturnLoc, /*DiscardedValue*/ false, + /*isConstexpr*/ false, FnRetType); if (ER.isInvalid()) return StmtError(); RetValExp = ER.get(); diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -4363,9 +4363,9 @@ if (Result.isInvalid()) return true; - Result = - ActOnFinishFullExpr(Result.getAs(), Param->getOuterLocStart(), - /*DiscardedValue*/ false); + Result = ActOnFinishFullExpr(Result.getAs(), Param->getOuterLocStart(), + /*DiscardedValue*/ false, /*isConstexpr*/ false, + Param->getType()); if (Result.isInvalid()) return true; diff --git a/clang/test/CXX/basic/basic.def.odr/p2.cpp b/clang/test/CXX/basic/basic.def.odr/p2.cpp --- a/clang/test/CXX/basic/basic.def.odr/p2.cpp +++ b/clang/test/CXX/basic/basic.def.odr/p2.cpp @@ -3,9 +3,11 @@ // RUN: %clang_cc1 -std=c++2a %s -Wno-unused -verify void use(int); - +#if __cplusplus < 201103L +// expected-no-diagnostics +#endif void f() { - const int a = 1; // expected-note {{here}} + const int a = 1; #if __cplusplus >= 201103L constexpr int arr[3] = {1, 2, 3}; // expected-note 2{{here}} @@ -57,9 +59,9 @@ // comma expression use((i, a)); - // FIXME: This is not an odr-use because it is a discarded-value + // This is not an odr-use because it is a discarded-value // expression applied to an expression whose potential result is 'a'. - use((a, a)); // expected-error {{reference to local variable}} + use((a, a)); // (and combinations thereof) use(a ? (i, a) : a); diff --git a/clang/test/CXX/drs/dr7xx.cpp b/clang/test/CXX/drs/dr7xx.cpp --- a/clang/test/CXX/drs/dr7xx.cpp +++ b/clang/test/CXX/drs/dr7xx.cpp @@ -20,7 +20,7 @@ namespace dr712 { // dr712: partial void use(int); void f() { - const int a = 0; // expected-note 5{{here}} + const int a = 0; struct X { void g(bool cond) { use(a); @@ -28,10 +28,10 @@ use(cond ? a : a); use((cond, a)); // expected-warning 2{{unused}} FIXME: should only warn once - (void)a; // FIXME: expected-error {{declared in enclosing}} - (void)(a); // FIXME: expected-error {{declared in enclosing}} - (void)(cond ? a : a); // FIXME: expected-error 2{{declared in enclosing}} - (void)(cond, a); // FIXME: expected-error {{declared in enclosing}} expected-warning {{unused}} + (void)a; + (void)(a); + (void)(cond ? a : a); + (void)((void) cond, a); } }; } @@ -39,14 +39,14 @@ #if __cplusplus >= 201103L void g() { struct A { int n; }; - constexpr A a = {0}; // expected-note 2{{here}} + constexpr A a = {0}; struct X { void g(bool cond) { use(a.n); use(a.*&A::n); - (void)a.n; // FIXME: expected-error {{declared in enclosing}} - (void)(a.*&A::n); // FIXME: expected-error {{declared in enclosing}} + (void)a.n; + (void)(a.*&A::n); } }; } diff --git a/clang/test/SemaCXX/PR25627-generic-lambdas-capturing.cpp b/clang/test/SemaCXX/PR25627-generic-lambdas-capturing.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/PR25627-generic-lambdas-capturing.cpp @@ -0,0 +1,326 @@ +// RUN: %clang_cc1 -std=c++17 -verify -fsyntax-only -fblocks -emit-llvm-only -Wno-unused-value %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 -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 + +constexpr int ODRUSE_SZ = sizeof(char); + +template +void f(T, const int (&)[N]) { } + +template +void f(const T&, const int (&)[ODRUSE_SZ]) { } + +#define DEFINE_SELECTOR(x) \ + int selector_ ## x[sizeof(x) == ODRUSE_SZ ? ODRUSE_SZ : ODRUSE_SZ + 5] + +#define F_CALL(x, a) f(x, selector_ ## a) + +// This is a risky assumption, because if an empty class gets captured by value +// the lambda's size will still be '1' +#define ASSERT_NO_CAPTURES(L) static_assert(sizeof(L) == 1, "size of closure with no captures must be 1") +#define ASSERT_CLOSURE_SIZE_EXACT(L, N) static_assert(sizeof(L) == (N), "size of closure must be " #N) +#define ASSERT_CLOSURE_SIZE(L, N) static_assert(sizeof(L) >= (N), "size of closure must be >=" #N) + + + +namespace sample { + struct X { + int i; + constexpr X(int i) : i(i) { } + }; + struct XVal { + constexpr XVal(X) { } + constexpr XVal() = default; + }; + struct XRef { + constexpr XRef(const X&) { } + }; +} + +namespace ns105 { + +template V f(V); +void f(...); +template void f(T&, T&, T); +struct X { }; +template auto foo() { + extern int ei; + extern const U eu; + constexpr X x; + constexpr U u; + constexpr int I = 10; + auto L = [](auto a) { + return f(I); + return f(x,a); + return f(x); + U& u = eu; + const int &ir = ei; + return f(u); + }; + return L; +} + +} + + +struct X72 { + constexpr static int s = 10; + int i; + int arr[4] = {1, 2, 3, 4}; +}; +void foo1_72() { + constexpr X72 x{5}; + constexpr X72 y{15}; + constexpr X72 z{25}; + constexpr X72 w{25}; + constexpr int arr[] = { 1, 2, 3 }; + constexpr auto pMemI = &X72::i; + constexpr auto pMemArr = &X72::arr; + constexpr X72 XArr[3] = { 1, 2, 3 }; + (void)[](auto a, bool b, bool b2 = false) { + int j = X72::s; + int i = x.i; + + int i2 = arr[1]; + return (x, y).i; + return arr[a]; + return (b ? x : y).i; + return (b ? (b2 ? (x) : y) : ((b || b2) ? w : z)).*pMemI; + return (b ? (b2 ? (x) : y) : ((b || b2) ? w : z)).i; + return (b ? (b2 ? (x) : y) : ((b || b2) ? w : z)).arr[0]; + return ((b ? (b2 ? (x) : y) : ((b || b2) ? w : z)).*pMemArr)[a]; + return ((b ? (b2 ? (x) : y) : ((b || b2) ? w : z)).*pMemArr)[(b ? x : y).i]; + return XArr[3].arr[2]; + return (b ? XArr[0] : XArr[1]).i; + return (b ? XArr[0] : XArr[1]).arr[(b ? x : y).i]; + return (b ? (b2 ? (x) : XArr[0]) : ((b || b2) ? (y) : XArr[1])).arr[(b ? x : y).i]; + return (b ? (b2 ? (x) : XArr[0]) : ((b || b2) ? (x,y,z,w) : XArr[1])).arr[(b ? x : y).i]; + return ((b ? (b2 ? (x) : XArr[0]) : ((b || b2) ? (x,y,z,w) : XArr[1])).*pMemArr)[(b ? x : y).i]; + }(5,true); +} + +auto f_79_142() { + + struct X { int i = 10; constexpr X() = default; X(const int& ir) { } }; + constexpr X XC; + constexpr int I = 5, + J = 10, + K = 15, //expected-note{{declared here}} + L = 20, //expected-note{{declared here}} + M = 25, + N = 30; + + auto L2 = [](auto a, bool b) -> auto { //expected-note 2{{begins here}} + const decltype(a)& A = ((I), J); + (((I, J), (K, L)), M), (N,I), (J,K); + (((I, a ? J, K : N, M), (K, L)), M), (N,I), (J,K); + + return (((I, b ? J, K : N, M), (K, L)), M), (N,I), (J,K); + return (XC, I, XC).i; + if constexpr (false) { + int i = I; + + // even though this is never instantiated?! + const int &ir = L; //expected-error{{cannot be implicitly capture}} + if constexpr (sizeof(a) >= 0) { + int j = I; + + // even though this is never instantiated ?! + const int &ir = K; //expected-error{{cannot be implicitly capture}} + } + } + + void f2(int); + return J, f2(J), J; + + }; + +} + + +namespace test_transformations_in_templates { + +template void foo(T t) { + constexpr int I = 10; //expected-note 3{{declared here}} + + auto L0 = [] { return I; }; + auto L = [](auto a) { return I; }; + auto L1 = []() -> decltype(auto) { + return I; + }; + auto L2 = []() -> auto& { //expected-note{{begins here}} + return I; //expected-error{{cannot be implicitly capture}} + }; + auto L3 = [](auto) -> auto& { //expected-note{{begins here}} + return I; //expected-error{{cannot be implicitly capture}} + }; + auto L4 = [] { //expected-note{{begins here}} + auto &t = I; //expected-error{{cannot be implicitly capture}} + auto t2 = I; + }; + +} + + +template void foo2(T t) { + constexpr T I(10); //expected-note 3{{declared here}} + + auto L0 = [] { return I; }; + auto L = [](auto a) { return I; }; + auto L1 = []() -> decltype(auto) { + return I; + }; + auto L2 = []() -> auto& { //expected-note{{begins here}} + return I; //expected-error{{cannot be implicitly capture}} + }; + auto L3 = [](auto) -> auto& { //expected-note{{begins here}} + return I; //expected-error{{cannot be implicitly capture}} + }; + + auto L4 = [] { //expected-note{{begins here}} + auto &t = I; //expected-error{{cannot be implicitly capture}} + auto t2 = I; + }; + +} + + +template void foo3(T t) { + const T& I(10); //expected-note 4{{declared here}} + const int &I2(20); //expected-note 2{{declared here}} + auto L0 = [] { //expected-note{{begins here}} + return I; // <-- should we diagnose this early? + return I2; //expected-error{{cannot be implicitly capture}} + }; + auto L = [](auto a) { //expected-note{{begins here}} + return I; // <-- should we diagnose this early? + return I2; //expected-error{{cannot be implicitly capture}} + }; + + // decltype(auto) is a reference type here. + auto L1 = []() -> decltype(auto) { //expected-note{{begins here}} + return I; //expected-error{{cannot be implicitly capture}} + }; + auto L2 = []() -> auto& { //expected-note{{begins here}} + return I; //expected-error{{cannot be implicitly capture}} + }; + auto L3 = [](auto) -> auto& { //expected-note{{begins here}} + return I; //expected-error{{cannot be implicitly capture}} + }; + + auto L4 = [] { //expected-note{{begins here}} + auto &t = I; //expected-error{{cannot be implicitly capture}} + auto t2 = I; + }; + +} + +template auto foo4(T t) { + const T& I(10); //#1 expected-note 2{{declared here}} + const T& J(t); //#2 expected-note 2{{declared here}} + constexpr T IC(20); + auto L5 = [] (auto a) -> decltype(auto) { //#3 expected-note 3{{begins here}} + decltype(auto) ir = I; //#4 expected-error{{cannot be implicitly capture}} + decltype(auto) jr = J; //#5 expected-error{{cannot be implicitly capture}} + auto x = J; // #6 + auto x2 = IC; // #7 + decltype(a) x3 = IC; + + return I; //#8 expected-error{{cannot be implicitly capture}} + }; + auto L6 = [] (auto) -> decltype(auto) { //#9 expected-note{{begins here}} + return J; //#10 expected-error{{cannot be implicitly capture}} + }; + return L5; +} + + +void f87_83() { + constexpr int I = 0;//expected-note {{declared here}} + + [] { //expected-note{{begins here}} + { int i = ((void)I, 0); } // OK + { int i = ((void)&I, 0); } //expected-error{{cannot be implicitly capture}} + }(); +} + + +void foo4_144() { + struct X { + int i; + constexpr X(int i) : i(i) { } + }; + constexpr X x(1); // #1_foo4_144 + auto L = [](auto a) { // #2_foo4_144 + using A = decltype(a); + int i = x.i; + auto i2 = x.i; + A a1 = x.i; // #3_foo4_144 + return x.i; + }; + L(5); // should have no errors here + + + { + struct Y { + Y() = default; + Y(const int&) { }; + }; + // we expect capture errors here - since Y's ctor takes an int by ref. + + //expected-error@#3_foo4_144 {{cannot be implicitly capture}} + //expected-note@#1_foo4_144 {{declared here}} + //expected-note@#2_foo4_144 {{begins here}} + L(Y()); //expected-note {{in instantiation}} + } +} + + +template auto foo5(T t) { + + constexpr T I(20); // #1a + auto L = [] (auto a) { // #2a + using A = decltype(a); + + auto i = I; // #3a + A a1 = I; // #4a + const A &ar = I; // #5a + + return I; // #6a + }; + return L; +} + +void intantiate() { + { + using namespace sample; + + + auto L = foo5(5); // no errors during instantiation of f + L(X(5)); // no errors during instantiation of L + + { + // expect errors here since we can not copy a structure since copy ctor requires binding by reference. + //auto L2 = foo5(X(5)); + + } + + } + + { + //expected-error@#4 {{cannot be implicitly capture}} + //expected-error@#5 {{cannot be implicitly capture}} + //expected-error@#6 {{cannot be implicitly capture}} + //expected-error@#8 {{cannot be implicitly capture}} + //expected-error@#10 {{cannot be implicitly capture}} + + //expected-note@#1 2{{declared here}} + //expected-note@#2 3{{declared here}} + //expected-note@#3 4{{begins here}} + //expected-note@#9 {{begins here}} + auto L1 = foo4(3); //expected-note{{in instantiation}} + } + +} +}