C++1y: track object lifetime during constexpr evaluation, and don't allow

objects to be used once their lifetimes end. This completes the C++1y
constexpr extensions.

llvm-svn: 187025
This commit is contained in:
Richard Smith 2013-07-24 07:11:57 +00:00
parent 6bcb34b180
commit 08d6a2cc7a
6 changed files with 249 additions and 61 deletions

View File

@ -93,6 +93,9 @@ def note_constexpr_lifetime_ended : Note<
def note_constexpr_access_uninit : Note<
"%select{read of|assignment to|increment of|decrement of}0 "
"object outside its lifetime is not allowed in a constant expression">;
def note_constexpr_use_uninit_reference : Note<
"use of reference outside its lifetime "
"is not allowed in a constant expression">;
def note_constexpr_modify_const_type : Note<
"modification of object of const-qualified type %0 is not allowed "
"in a constant expression">;

View File

@ -317,6 +317,12 @@ namespace {
const FunctionDecl *Callee, const LValue *This,
APValue *Arguments);
~CallStackFrame();
APValue *getTemporary(const void *Key) {
MapTy::iterator I = Temporaries.find(Key);
return I == Temporaries.end() ? 0 : &I->second;
}
APValue &createTemporary(const void *Key, bool IsLifetimeExtended);
};
/// Temporarily override 'this'.
@ -369,6 +375,20 @@ namespace {
}
};
/// A cleanup, and a flag indicating whether it is lifetime-extended.
class Cleanup {
llvm::PointerIntPair<APValue*, 1, bool> Value;
public:
Cleanup(APValue *Val, bool IsLifetimeExtended)
: Value(Val, IsLifetimeExtended) {}
bool isLifetimeExtended() const { return Value.getInt(); }
void endLifetime() {
*Value.getPointer() = APValue();
}
};
/// EvalInfo - This is a private struct used by the evaluator to capture
/// information about a subexpression as it is folded. It retains information
/// about the AST context, but also maintains information about the folded
@ -407,6 +427,10 @@ namespace {
/// initialized after CurrentCall and CallStackDepth.
CallStackFrame BottomFrame;
/// A stack of values whose lifetimes end at the end of some surrounding
/// evaluation frame.
llvm::SmallVector<Cleanup, 16> CleanupStack;
/// EvaluatingDecl - This is the declaration whose initializer is being
/// evaluated, if any.
APValue::LValueBase EvaluatingDecl;
@ -423,7 +447,7 @@ namespace {
/// expression is a potential constant expression? If so, some diagnostics
/// are suppressed.
bool CheckingPotentialConstantExpression;
bool IntOverflowCheckMode;
EvalInfo(const ASTContext &C, Expr::EvalStatus &S,
@ -601,6 +625,42 @@ namespace {
Info.EvalStatus = Old;
}
};
/// RAII object wrapping a full-expression or block scope, and handling
/// the ending of the lifetime of temporaries created within it.
template<bool IsFullExpression>
class ScopeRAII {
EvalInfo &Info;
unsigned OldStackSize;
public:
ScopeRAII(EvalInfo &Info)
: Info(Info), OldStackSize(Info.CleanupStack.size()) {}
~ScopeRAII() {
// Body moved to a static method to encourage the compiler to inline away
// instances of this class.
cleanup(Info, OldStackSize);
}
private:
static void cleanup(EvalInfo &Info, unsigned OldStackSize) {
unsigned NewEnd = OldStackSize;
for (unsigned I = OldStackSize, N = Info.CleanupStack.size();
I != N; ++I) {
if (IsFullExpression && Info.CleanupStack[I].isLifetimeExtended()) {
// Full-expression cleanup of a lifetime-extended temporary: nothing
// to do, just move this cleanup to the right place in the stack.
std::swap(Info.CleanupStack[I], Info.CleanupStack[NewEnd]);
++NewEnd;
} else {
// End the lifetime of the object.
Info.CleanupStack[I].endLifetime();
}
}
Info.CleanupStack.erase(Info.CleanupStack.begin() + NewEnd,
Info.CleanupStack.end());
}
};
typedef ScopeRAII<false> BlockScopeRAII;
typedef ScopeRAII<true> FullExpressionRAII;
}
bool SubobjectDesignator::checkSubobject(EvalInfo &Info, const Expr *E,
@ -643,6 +703,14 @@ CallStackFrame::~CallStackFrame() {
Info.CurrentCall = Caller;
}
APValue &CallStackFrame::createTemporary(const void *Key,
bool IsLifetimeExtended) {
APValue &Result = Temporaries[Key];
assert(Result.isUninit() && "temporary created multiple times");
Info.CleanupStack.push_back(Cleanup(&Result, IsLifetimeExtended));
return Result;
}
static void describeCall(CallStackFrame *Frame, raw_ostream &Out);
void EvalInfo::addCallStack(unsigned Limit) {
@ -1710,11 +1778,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// If this is a local variable, dig out its value.
if (Frame) {
Result = &Frame->Temporaries[VD];
// If we've carried on past an unevaluatable local variable initializer,
// we can't go any further. This can happen during potential constant
// expression checking.
return !Result->isUninit();
Result = Frame->getTemporary(VD);
assert(Result && "missing value for local variable");
return true;
}
// Dig out the initializer, and use the declaration which it's attached to.
@ -1731,7 +1797,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// in-flight value.
if (Info.EvaluatingDecl.dyn_cast<const ValueDecl*>() == VD) {
Result = Info.EvaluatingDeclValue;
return !Result->isUninit();
return true;
}
// Never evaluate the initializer of a weak variable. We can't be sure that
@ -1884,16 +1950,20 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
Info.Diag(E);
return handler.failed();
}
if (Sub.Entries.empty())
return handler.found(*Obj.Value, Obj.Type);
if (Info.CheckingPotentialConstantExpression && Obj.Value->isUninit())
// This object might be initialized later.
return handler.failed();
APValue *O = Obj.Value;
QualType ObjType = Obj.Type;
// Walk the designator's path to find the subobject.
for (unsigned I = 0, N = Sub.Entries.size(); I != N; ++I) {
for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) {
if (O->isUninit()) {
if (!Info.CheckingPotentialConstantExpression)
Info.Diag(E, diag::note_constexpr_access_uninit) << handler.AccessKind;
return handler.failed();
}
if (I == N)
return handler.found(*O, ObjType);
if (ObjType->isArrayType()) {
// Next subobject is an array element.
const ConstantArrayType *CAT = Info.Ctx.getAsConstantArrayType(ObjType);
@ -2006,15 +2076,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
if (WasConstQualified)
ObjType.addConst();
}
if (O->isUninit()) {
if (!Info.CheckingPotentialConstantExpression)
Info.Diag(E, diag::note_constexpr_access_uninit) << handler.AccessKind;
return handler.failed();
}
}
return handler.found(*O, ObjType);
}
namespace {
@ -2328,7 +2390,8 @@ CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, AccessKinds AK,
return CompleteObject();
}
} else {
BaseVal = &Frame->Temporaries[Base];
BaseVal = Frame->getTemporary(Base);
assert(BaseVal && "missing value for temporary");
}
// Volatile temporary objects cannot be accessed in constant expressions.
@ -2887,7 +2950,7 @@ static bool EvaluateDecl(EvalInfo &Info, const Decl *D) {
LValue Result;
Result.set(VD, Info.CurrentCall->Index);
APValue &Val = Info.CurrentCall->Temporaries[VD];
APValue &Val = Info.CurrentCall->createTemporary(VD, true);
if (!VD->getInit()) {
Info.Diag(D->getLocStart(), diag::note_constexpr_uninitialized)
@ -2910,6 +2973,7 @@ static bool EvaluateDecl(EvalInfo &Info, const Decl *D) {
/// Evaluate a condition (either a variable declaration or an expression).
static bool EvaluateCond(EvalInfo &Info, const VarDecl *CondDecl,
const Expr *Cond, bool &Result) {
FullExpressionRAII Scope(Info);
if (CondDecl && !EvaluateDecl(Info, CondDecl))
return false;
return EvaluateAsBooleanCondition(Cond, Result, Info);
@ -2922,6 +2986,7 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
static EvalStmtResult EvaluateLoopBody(APValue &Result, EvalInfo &Info,
const Stmt *Body,
const SwitchCase *Case = 0) {
BlockScopeRAII Scope(Info);
switch (EvalStmtResult ESR = EvaluateStmt(Result, Info, Body, Case)) {
case ESR_Break:
return ESR_Succeeded;
@ -2939,13 +3004,18 @@ static EvalStmtResult EvaluateLoopBody(APValue &Result, EvalInfo &Info,
/// Evaluate a switch statement.
static EvalStmtResult EvaluateSwitch(APValue &Result, EvalInfo &Info,
const SwitchStmt *SS) {
BlockScopeRAII Scope(Info);
// Evaluate the switch condition.
if (SS->getConditionVariable() &&
!EvaluateDecl(Info, SS->getConditionVariable()))
return ESR_Failed;
APSInt Value;
if (!EvaluateInteger(SS->getCond(), Value, Info))
return ESR_Failed;
{
FullExpressionRAII Scope(Info);
if (SS->getConditionVariable() &&
!EvaluateDecl(Info, SS->getConditionVariable()))
return ESR_Failed;
if (!EvaluateInteger(SS->getCond(), Value, Info))
return ESR_Failed;
}
// Find the switch case corresponding to the value of the condition.
// FIXME: Cache this lookup.
@ -3021,6 +3091,11 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
// FIXME: Precompute which side of an 'if' we would jump to, and go
// straight there rather than scanning both sides.
const IfStmt *IS = cast<IfStmt>(S);
// Wrap the evaluation in a block scope, in case it's a DeclStmt
// preceded by our switch label.
BlockScopeRAII Scope(Info);
EvalStmtResult ESR = EvaluateStmt(Result, Info, IS->getThen(), Case);
if (ESR != ESR_CaseNotFound || !IS->getElse())
return ESR;
@ -3041,8 +3116,11 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
EvaluateLoopBody(Result, Info, FS->getBody(), Case);
if (ESR != ESR_Continue)
return ESR;
if (FS->getInc() && !EvaluateIgnoredValue(Info, FS->getInc()))
return ESR_Failed;
if (FS->getInc()) {
FullExpressionRAII IncScope(Info);
if (!EvaluateIgnoredValue(Info, FS->getInc()))
return ESR_Failed;
}
break;
}
@ -3054,13 +3132,12 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
}
}
// FIXME: Mark all temporaries in the current frame as destroyed at
// the end of each full-expression.
switch (S->getStmtClass()) {
default:
if (const Expr *E = dyn_cast<Expr>(S)) {
// Don't bother evaluating beyond an expression-statement which couldn't
// be evaluated.
FullExpressionRAII Scope(Info);
if (!EvaluateIgnoredValue(Info, E))
return ESR_Failed;
return ESR_Succeeded;
@ -3075,20 +3152,28 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
case Stmt::DeclStmtClass: {
const DeclStmt *DS = cast<DeclStmt>(S);
for (DeclStmt::const_decl_iterator DclIt = DS->decl_begin(),
DclEnd = DS->decl_end(); DclIt != DclEnd; ++DclIt)
DclEnd = DS->decl_end(); DclIt != DclEnd; ++DclIt) {
// Each declaration initialization is its own full-expression.
// FIXME: This isn't quite right; if we're performing aggregate
// initialization, each braced subexpression is its own full-expression.
FullExpressionRAII Scope(Info);
if (!EvaluateDecl(Info, *DclIt) && !Info.keepEvaluatingAfterFailure())
return ESR_Failed;
}
return ESR_Succeeded;
}
case Stmt::ReturnStmtClass: {
const Expr *RetExpr = cast<ReturnStmt>(S)->getRetValue();
FullExpressionRAII Scope(Info);
if (RetExpr && !Evaluate(Result, Info, RetExpr))
return ESR_Failed;
return ESR_Returned;
}
case Stmt::CompoundStmtClass: {
BlockScopeRAII Scope(Info);
const CompoundStmt *CS = cast<CompoundStmt>(S);
for (CompoundStmt::const_body_iterator BI = CS->body_begin(),
BE = CS->body_end(); BI != BE; ++BI) {
@ -3105,6 +3190,7 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
const IfStmt *IS = cast<IfStmt>(S);
// Evaluate the condition, as either a var decl or as an expression.
BlockScopeRAII Scope(Info);
bool Cond;
if (!EvaluateCond(Info, IS->getConditionVariable(), IS->getCond(), Cond))
return ESR_Failed;
@ -3120,6 +3206,7 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
case Stmt::WhileStmtClass: {
const WhileStmt *WS = cast<WhileStmt>(S);
while (true) {
BlockScopeRAII Scope(Info);
bool Continue;
if (!EvaluateCond(Info, WS->getConditionVariable(), WS->getCond(),
Continue))
@ -3143,6 +3230,7 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
return ESR;
Case = 0;
FullExpressionRAII CondScope(Info);
if (!EvaluateAsBooleanCondition(DS->getCond(), Continue, Info))
return ESR_Failed;
} while (Continue);
@ -3151,12 +3239,14 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
case Stmt::ForStmtClass: {
const ForStmt *FS = cast<ForStmt>(S);
BlockScopeRAII Scope(Info);
if (FS->getInit()) {
EvalStmtResult ESR = EvaluateStmt(Result, Info, FS->getInit());
if (ESR != ESR_Succeeded)
return ESR;
}
while (true) {
BlockScopeRAII Scope(Info);
bool Continue = true;
if (FS->getCond() && !EvaluateCond(Info, FS->getConditionVariable(),
FS->getCond(), Continue))
@ -3168,14 +3258,18 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
if (ESR != ESR_Continue)
return ESR;
if (FS->getInc() && !EvaluateIgnoredValue(Info, FS->getInc()))
return ESR_Failed;
if (FS->getInc()) {
FullExpressionRAII IncScope(Info);
if (!EvaluateIgnoredValue(Info, FS->getInc()))
return ESR_Failed;
}
}
return ESR_Succeeded;
}
case Stmt::CXXForRangeStmtClass: {
const CXXForRangeStmt *FS = cast<CXXForRangeStmt>(S);
BlockScopeRAII Scope(Info);
// Initialize the __range variable.
EvalStmtResult ESR = EvaluateStmt(Result, Info, FS->getRangeStmt());
@ -3189,13 +3283,17 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info,
while (true) {
// Condition: __begin != __end.
bool Continue = true;
if (!EvaluateAsBooleanCondition(FS->getCond(), Continue, Info))
return ESR_Failed;
if (!Continue)
break;
{
bool Continue = true;
FullExpressionRAII CondExpr(Info);
if (!EvaluateAsBooleanCondition(FS->getCond(), Continue, Info))
return ESR_Failed;
if (!Continue)
break;
}
// User's variable declaration, initialized by *__begin.
BlockScopeRAII InnerScope(Info);
ESR = EvaluateStmt(Result, Info, FS->getLoopVarStmt());
if (ESR != ESR_Succeeded)
return ESR;
@ -3410,6 +3508,9 @@ static bool HandleConstructorCall(SourceLocation CallLoc, const LValue &This,
if (RD->isInvalidDecl()) return false;
const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD);
// A scope for temporaries lifetime-extended by reference members.
BlockScopeRAII LifetimeExtendedScope(Info);
bool Success = true;
unsigned BasesSeen = 0;
#ifndef NDEBUG
@ -3476,6 +3577,7 @@ static bool HandleConstructorCall(SourceLocation CallLoc, const LValue &This,
llvm_unreachable("unknown base initializer kind");
}
FullExpressionRAII InitScope(Info);
if (!EvaluateInPlace(*Value, Info, Subobject, (*I)->getInit())) {
// If we're checking for a potential constant expression, evaluate all
// initializers even if some of them fail.
@ -3633,7 +3735,7 @@ public:
RetTy VisitBinaryConditionalOperator(const BinaryConditionalOperator *E) {
// Evaluate and cache the common expression. We treat it as a temporary,
// even though it's not quite the same thing.
if (!Evaluate(Info.CurrentCall->Temporaries[E->getOpaqueValue()],
if (!Evaluate(Info.CurrentCall->createTemporary(E->getOpaqueValue(), false),
Info, E->getCommon()))
return false;
@ -3668,18 +3770,17 @@ public:
}
RetTy VisitOpaqueValueExpr(const OpaqueValueExpr *E) {
APValue &Value = Info.CurrentCall->Temporaries[E];
if (Value.isUninit()) {
const Expr *Source = E->getSourceExpr();
if (!Source)
return Error(E);
if (Source == E) { // sanity checking.
assert(0 && "OpaqueValueExpr recursively refers to itself");
return Error(E);
}
return StmtVisitorTy::Visit(Source);
if (APValue *Value = Info.CurrentCall->getTemporary(E))
return DerivedSuccess(*Value, E);
const Expr *Source = E->getSourceExpr();
if (!Source)
return Error(E);
if (Source == E) { // sanity checking.
assert(0 && "OpaqueValueExpr recursively refers to itself");
return Error(E);
}
return DerivedSuccess(Value, E);
return StmtVisitorTy::Visit(Source);
}
RetTy VisitCallExpr(const CallExpr *E) {
@ -3870,6 +3971,7 @@ public:
if (Info.getIntOverflowCheckMode())
return Error(E);
BlockScopeRAII Scope(Info);
const CompoundStmt *CS = E->getSubStmt();
for (CompoundStmt::const_body_iterator BI = CS->body_begin(),
BE = CS->body_end();
@ -4121,6 +4223,11 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
APValue *V;
if (!evaluateVarDeclInit(Info, E, VD, Frame, V))
return false;
if (V->isUninit()) {
if (!Info.CheckingPotentialConstantExpression)
Info.Diag(E, diag::note_constexpr_use_uninit_reference);
return false;
}
return Success(*V, E);
}
@ -4146,7 +4253,8 @@ bool LValueExprEvaluator::VisitMaterializeTemporaryExpr(
*Value = APValue();
Result.set(E);
} else {
Value = &Info.CurrentCall->Temporaries[E];
Value = &Info.CurrentCall->
createTemporary(E, E->getStorageDuration() == SD_Automatic);
Result.set(E, Info.CurrentCall->Index);
}
@ -4490,7 +4598,7 @@ bool PointerExprEvaluator::VisitCastExpr(const CastExpr* E) {
return false;
} else {
Result.set(SubExpr, Info.CurrentCall->Index);
if (!EvaluateInPlace(Info.CurrentCall->Temporaries[SubExpr],
if (!EvaluateInPlace(Info.CurrentCall->createTemporary(SubExpr, false),
Info, Result, SubExpr))
return false;
}
@ -4940,7 +5048,8 @@ public:
/// Visit an expression which constructs the value of this temporary.
bool VisitConstructExpr(const Expr *E) {
Result.set(E, Info.CurrentCall->Index);
return EvaluateInPlace(Info.CurrentCall->Temporaries[E], Info, Result, E);
return EvaluateInPlace(Info.CurrentCall->createTemporary(E, false),
Info, Result, E);
}
bool VisitCastExpr(const CastExpr *E) {
@ -7644,15 +7753,17 @@ static bool Evaluate(APValue &Result, EvalInfo &Info, const Expr *E) {
} else if (T->isArrayType()) {
LValue LV;
LV.set(E, Info.CurrentCall->Index);
if (!EvaluateArray(E, LV, Info.CurrentCall->Temporaries[E], Info))
APValue &Value = Info.CurrentCall->createTemporary(E, false);
if (!EvaluateArray(E, LV, Value, Info))
return false;
Result = Info.CurrentCall->Temporaries[E];
Result = Value;
} else if (T->isRecordType()) {
LValue LV;
LV.set(E, Info.CurrentCall->Index);
if (!EvaluateRecord(E, LV, Info.CurrentCall->Temporaries[E], Info))
APValue &Value = Info.CurrentCall->createTemporary(E, false);
if (!EvaluateRecord(E, LV, Value, Info))
return false;
Result = Info.CurrentCall->Temporaries[E];
Result = Value;
} else if (T->isVoidType()) {
if (!Info.getLangOpts().CPlusPlus11)
Info.CCEDiag(E, diag::note_constexpr_nonliteral)

View File

@ -1709,3 +1709,21 @@ namespace ConstexprConstructorRecovery {
};
constexpr X x{};
}
namespace Lifetime {
void f() {
constexpr int &n = n; // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime}} expected-warning {{not yet bound to a value}}
constexpr int m = m; // expected-error {{constant expression}} expected-note {{read of object outside its lifetime}}
}
constexpr int &get(int &&n) { return n; }
struct S {
int &&r; // expected-note 2{{declared here}}
int &s;
int t;
constexpr S() : r(0), s(get(0)), t(r) {} // expected-warning {{temporary}}
constexpr S(int) : r(0), s(get(0)), t(s) {} // expected-warning {{temporary}} expected-note {{read of object outside its lifetime}}
};
constexpr int k1 = S().t; // ok, int is lifetime-extended to end of constructor
constexpr int k2 = S(0).t; // expected-error {{constant expression}} expected-note {{in call}}
}

View File

@ -814,3 +814,59 @@ namespace VirtualFromBase {
constexpr X<S2> *q = const_cast<X<X<S2>>*>(&xxs2);
static_assert(q->f() == sizeof(X<S2>), ""); // expected-error {{constant expression}} expected-note {{virtual function call}}
}
namespace Lifetime {
constexpr int &get(int &&r) { return r; }
constexpr int f() {
int &r = get(123);
return r; // expected-note {{read of object outside its lifetime}}
}
static_assert(f() == 123, ""); // expected-error {{constant expression}} expected-note {{in call}}
constexpr int g() {
int *p = 0;
{
int n = 0;
p = &n;
n = 42;
}
*p = 123; // expected-note {{assignment to object outside its lifetime}}
return *p;
}
static_assert(g() == 42, ""); // expected-error {{constant expression}} expected-note {{in call}}
constexpr int h(int n) {
int *p[4] = {};
int &&r = 1;
p[0] = &r;
while (int a = 1) {
p[1] = &a;
for (int b = 1; int c = 1; ) {
p[2] = &b, p[3] = &c;
break;
}
break;
}
*p[n] = 0; // expected-note 3{{assignment to object outside its lifetime}}
return *p[n];
}
static_assert(h(0) == 0, ""); // ok, lifetime-extended
static_assert(h(1) == 0, ""); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(h(2) == 0, ""); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(h(3) == 0, ""); // expected-error {{constant expression}} expected-note {{in call}}
// FIXME: This function should be treated as non-constant.
constexpr void lifetime_versus_loops() {
int *p = 0;
for (int i = 0; i != 2; ++i) {
int *q = p;
int n = 0;
p = &n;
if (i)
// This modifies the 'n' from the previous iteration of the loop outside
// its lifetime.
++*q;
}
}
static_assert((lifetime_versus_loops(), true), "");
}

View File

@ -16,7 +16,7 @@ void f() {
}
int a() {
const int t=t; // expected-note {{declared here}}
const int t=t; // expected-note {{declared here}} expected-note {{read of object outside its lifetime}}
switch(1) { // expected-warning {{no case matching constant switch condition '1'}}
case t:; // expected-error {{not an integral constant expression}} expected-note {{initializer of 't' is not a constant expression}}
}

View File

@ -457,7 +457,7 @@ available.</p>
<tr>
<td>Relaxing requirements on constexpr functions</td>
<td><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3652.html">N3652</a></td>
<td class="partial" align="center">Partial</td>
<td class="svn" align="center">SVN</td>
</tr>
<tr>
<td>Member initializers and aggregates</td>