/// would be constexpr.
bool defaultedDefaultConstructorIsConstexpr() const {
return data().DefaultedDefaultConstructorIsConstexpr &&
- (!isUnion() || hasInClassInitializer() || !hasVariantMembers());
+ (!isUnion() || hasInClassInitializer() || !hasVariantMembers() ||
+ getASTContext().getLangOpts().CPlusPlus2a);
}
/// Determine whether this class has a constexpr default constructor.
def note_constexpr_pure_virtual_call : Note<
"pure virtual function %q0 called">;
def note_constexpr_polymorphic_unknown_dynamic_type : Note<
- "%select{||||virtual function called on|dynamic_cast applied to|"
+ "%select{|||||virtual function called on|dynamic_cast applied to|"
"typeid applied to}0 object '%1' whose dynamic type is not constant">;
def note_constexpr_dynamic_cast_to_reference_failed : Note<
"reference dynamic_cast failed: %select{"
"%select{|implicit }0use of 'this' pointer is only allowed within the "
"evaluation of a call to a 'constexpr' member function">;
def note_constexpr_lifetime_ended : Note<
- "%select{read of|assignment to|increment of|decrement of|member call on|"
- "dynamic_cast of|typeid applied to}0 "
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to}0 "
"%select{temporary|variable}1 whose lifetime has ended">;
def note_constexpr_access_uninit : Note<
- "%select{read of|assignment to|increment of|decrement of|member call on|"
- "dynamic_cast of|typeid applied to}0 "
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to}0 "
"%select{object outside its lifetime|uninitialized object}1 "
"is not allowed in a constant expression">;
def note_constexpr_use_uninit_reference : Note<
"modification of object of const-qualified type %0 is not allowed "
"in a constant expression">;
def note_constexpr_access_volatile_type : Note<
- "%select{read of|assignment to|increment of|decrement of|<ERROR>|<ERROR>}0 "
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "<ERROR>|<ERROR>}0 "
"volatile-qualified type %1 is not allowed in a constant expression">;
def note_constexpr_access_volatile_obj : Note<
- "%select{read of|assignment to|increment of|decrement of|<ERROR>|<ERROR>}0 "
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "<ERROR>|<ERROR>}0 "
"volatile %select{temporary|object %2|member %2}1 is not allowed in "
"a constant expression">;
def note_constexpr_volatile_here : Note<
def note_constexpr_ltor_incomplete_type : Note<
"read of incomplete type %0 is not allowed in a constant expression">;
def note_constexpr_access_null : Note<
- "%select{read of|assignment to|increment of|decrement of|member call on|"
- "dynamic_cast of|typeid applied to}0 "
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to}0 "
"dereferenced null pointer is not allowed in a constant expression">;
def note_constexpr_access_past_end : Note<
- "%select{read of|assignment to|increment of|decrement of|member call on|"
- "dynamic_cast of|typeid applied to}0 "
- "dereferenced one-past-the-end pointer is not allowed in a constant expression">;
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to}0 "
+ "dereferenced one-past-the-end pointer is not allowed "
+ "in a constant expression">;
def note_constexpr_access_unsized_array : Note<
- "%select{read of|assignment to|increment of|decrement of|member call on|"
- "dynamic_cast of|typeid applied to}0 "
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to}0 "
"element of array without known bound "
"is not allowed in a constant expression">;
def note_constexpr_access_inactive_union_member : Note<
- "%select{read of|assignment to|increment of|decrement of|member call on|"
- "dynamic_cast of|typeid applied to}0 "
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to}0 "
"member %1 of union with %select{active member %3|no active member}2 "
"is not allowed in a constant expression">;
def note_constexpr_access_static_temporary : Note<
- "%select{read of|assignment to|increment of|decrement of|member call on|"
- "dynamic_cast of|typeid applied to}0 temporary "
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to}0 temporary "
"is not allowed in a constant expression outside the expression that "
"created the temporary">;
def note_constexpr_access_unreadable_object : Note<
- "%select{read of|assignment to|increment of|decrement of|member call on|"
- "dynamic_cast of|typeid applied to}0 object '%1' whose value is not known">;
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to}0 object '%1' "
+ "whose value is not known">;
def note_constexpr_modify_global : Note<
"a constant expression cannot modify an object that is visible outside "
"that expression">;
def err_constexpr_local_var_non_literal_type : Error<
"variable of non-literal type %1 cannot be defined in a constexpr "
"%select{function|constructor}0">;
-def err_constexpr_local_var_no_init : Error<
- "variables defined in a constexpr %select{function|constructor}0 must be "
- "initialized">;
+def ext_constexpr_local_var_no_init : ExtWarn<
+ "uninitialized variable in a constexpr %select{function|constructor}0 "
+ "is a C++20 extension">, InGroup<CXX2a>;
+def warn_cxx17_compat_constexpr_local_var_no_init : Warning<
+ "uninitialized variable in a constexpr %select{function|constructor}0 "
+ "is incompatible with C++ standards before C++20">,
+ InGroup<CXXPre2aCompat>, DefaultIgnore;
def ext_constexpr_function_never_constant_expr : ExtWarn<
"constexpr %select{function|constructor}0 never produces a "
"constant expression">, InGroup<DiagGroup<"invalid-constexpr">>, DefaultError;
def err_constexpr_function_try_block : Error<
"function try block not allowed in constexpr %select{function|constructor}0">;
-// c++2a function try blocks in constexpr
+// C++2a function try blocks in constexpr
def ext_constexpr_function_try_block_cxx2a : ExtWarn<
"function try block in constexpr %select{function|constructor}0 is "
"a C++2a extension">, InGroup<CXX2a>;
"incompatible with C++ standards before C++2a">,
InGroup<CXXPre2aCompat>, DefaultIgnore;
-def err_constexpr_union_ctor_no_init : Error<
- "constexpr union constructor does not initialize any member">;
-def err_constexpr_ctor_missing_init : Error<
- "constexpr constructor must initialize all members">;
+def ext_constexpr_union_ctor_no_init : ExtWarn<
+ "constexpr union constructor that does not initialize any member "
+ "is a C++20 extension">, InGroup<CXX2a>;
+def warn_cxx17_compat_constexpr_union_ctor_no_init : Warning<
+ "constexpr union constructor that does not initialize any member "
+ "is incompatible with C++ standards before C++20">,
+ InGroup<CXXPre2aCompat>, DefaultIgnore;
+def ext_constexpr_ctor_missing_init : ExtWarn<
+ "constexpr constructor that does not initialize all members "
+ "is a C++20 extension">, InGroup<CXX2a>;
+def warn_cxx17_compat_constexpr_ctor_missing_init : Warning<
+ "constexpr constructor that does not initialize all members "
+ "is incompatible with C++ standards before C++20">,
+ InGroup<CXXPre2aCompat>, DefaultIgnore;
def note_constexpr_ctor_missing_init : Note<
"member not initialized by constructor">;
def note_non_literal_no_constexpr_ctors : Note<
} else {
// Base element type of field is a non-class type.
if (!T->isLiteralType(Context) ||
- (!Field->hasInClassInitializer() && !isUnion()))
+ (!Field->hasInClassInitializer() && !isUnion() &&
+ !Context.getLangOpts().CPlusPlus2a))
data().DefaultedDefaultConstructorIsConstexpr = false;
// C++11 [class.copy]p23:
return Result;
}
+static bool isRead(AccessKinds AK) {
+ return AK == AK_Read || AK == AK_ReadObjectRepresentation;
+}
+
static bool isModification(AccessKinds AK) {
switch (AK) {
case AK_Read:
+ case AK_ReadObjectRepresentation:
case AK_MemberCall:
case AK_DynamicCast:
case AK_TypeId:
/// Is this an access per the C++ definition?
static bool isFormalAccess(AccessKinds AK) {
- return AK == AK_Read || isModification(AK);
+ return isRead(AK) || isModification(AK);
}
namespace {
return false;
}
-/// Check that this core constant expression value is a valid value for a
-/// constant expression. If not, report an appropriate diagnostic. Does not
-/// check that the expression is of literal type.
+enum class CheckEvaluationResultKind {
+ ConstantExpression,
+ FullyInitialized,
+};
+
static bool
-CheckConstantExpression(EvalInfo &Info, SourceLocation DiagLoc, QualType Type,
- const APValue &Value,
- Expr::ConstExprUsage Usage = Expr::EvaluateForCodeGen,
- SourceLocation SubobjectLoc = SourceLocation()) {
+CheckEvaluationResult(CheckEvaluationResultKind CERK, EvalInfo &Info,
+ SourceLocation DiagLoc, QualType Type,
+ const APValue &Value, Expr::ConstExprUsage Usage,
+ SourceLocation SubobjectLoc = SourceLocation()) {
if (!Value.hasValue()) {
Info.FFDiag(DiagLoc, diag::note_constexpr_uninitialized)
<< true << Type;
if (Value.isArray()) {
QualType EltTy = Type->castAsArrayTypeUnsafe()->getElementType();
for (unsigned I = 0, N = Value.getArrayInitializedElts(); I != N; ++I) {
- if (!CheckConstantExpression(Info, DiagLoc, EltTy,
- Value.getArrayInitializedElt(I), Usage,
- SubobjectLoc))
+ if (!CheckEvaluationResult(CERK, Info, DiagLoc, EltTy,
+ Value.getArrayInitializedElt(I), Usage,
+ SubobjectLoc))
return false;
}
if (!Value.hasArrayFiller())
return true;
- return CheckConstantExpression(Info, DiagLoc, EltTy, Value.getArrayFiller(),
- Usage, SubobjectLoc);
+ return CheckEvaluationResult(CERK, Info, DiagLoc, EltTy,
+ Value.getArrayFiller(), Usage, SubobjectLoc);
}
if (Value.isUnion() && Value.getUnionField()) {
- return CheckConstantExpression(Info, DiagLoc,
- Value.getUnionField()->getType(),
- Value.getUnionValue(), Usage,
- Value.getUnionField()->getLocation());
+ return CheckEvaluationResult(
+ CERK, Info, DiagLoc, Value.getUnionField()->getType(),
+ Value.getUnionValue(), Usage, Value.getUnionField()->getLocation());
}
if (Value.isStruct()) {
RecordDecl *RD = Type->castAs<RecordType>()->getDecl();
if (const CXXRecordDecl *CD = dyn_cast<CXXRecordDecl>(RD)) {
unsigned BaseIndex = 0;
for (const CXXBaseSpecifier &BS : CD->bases()) {
- if (!CheckConstantExpression(Info, DiagLoc, BS.getType(),
- Value.getStructBase(BaseIndex), Usage,
- BS.getBeginLoc()))
+ if (!CheckEvaluationResult(CERK, Info, DiagLoc, BS.getType(),
+ Value.getStructBase(BaseIndex), Usage,
+ BS.getBeginLoc()))
return false;
++BaseIndex;
}
if (I->isUnnamedBitfield())
continue;
- if (!CheckConstantExpression(Info, DiagLoc, I->getType(),
- Value.getStructField(I->getFieldIndex()),
- Usage, I->getLocation()))
+ if (!CheckEvaluationResult(CERK, Info, DiagLoc, I->getType(),
+ Value.getStructField(I->getFieldIndex()),
+ Usage, I->getLocation()))
return false;
}
}
- if (Value.isLValue()) {
+ if (Value.isLValue() &&
+ CERK == CheckEvaluationResultKind::ConstantExpression) {
LValue LVal;
LVal.setFrom(Info.Ctx, Value);
return CheckLValueConstantExpression(Info, DiagLoc, Type, LVal, Usage);
}
- if (Value.isMemberPointer())
+ if (Value.isMemberPointer() &&
+ CERK == CheckEvaluationResultKind::ConstantExpression)
return CheckMemberPointerConstantExpression(Info, DiagLoc, Type, Value, Usage);
// Everything else is fine.
return true;
}
+/// Check that this core constant expression value is a valid value for a
+/// constant expression. If not, report an appropriate diagnostic. Does not
+/// check that the expression is of literal type.
+static bool
+CheckConstantExpression(EvalInfo &Info, SourceLocation DiagLoc, QualType Type,
+ const APValue &Value,
+ Expr::ConstExprUsage Usage = Expr::EvaluateForCodeGen) {
+ return CheckEvaluationResult(CheckEvaluationResultKind::ConstantExpression,
+ Info, DiagLoc, Type, Value, Usage);
+}
+
+/// Check that this evaluated value is fully-initialized and can be loaded by
+/// an lvalue-to-rvalue conversion.
+static bool CheckFullyInitialized(EvalInfo &Info, SourceLocation DiagLoc,
+ QualType Type, const APValue &Value) {
+ return CheckEvaluationResult(CheckEvaluationResultKind::FullyInitialized,
+ Info, DiagLoc, Type, Value,
+ Expr::EvaluateForCodeGen);
+}
+
static bool EvalPointerValueAsBool(const APValue &Value, bool &Result) {
// A null base expression indicates a null pointer. These are always
// evaluatable, and they are false unless the offset is zero.
// Walk the designator's path to find the subobject.
for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) {
// Reading an indeterminate value is undefined, but assigning over one is OK.
- if (O->isAbsent() || (O->isIndeterminate() && handler.AccessKind != AK_Assign)) {
+ if (O->isAbsent() ||
+ (O->isIndeterminate() && handler.AccessKind != AK_Assign &&
+ handler.AccessKind != AK_ReadObjectRepresentation)) {
if (!Info.checkingPotentialConstantExpression())
Info.FFDiag(E, diag::note_constexpr_access_uninit)
<< handler.AccessKind << O->isIndeterminate();
// things we need to check: if there are any mutable subobjects, we
// cannot perform this read. (This only happens when performing a trivial
// copy or assignment.)
- if (ObjType->isRecordType() && handler.AccessKind == AK_Read &&
+ if (ObjType->isRecordType() && isRead(handler.AccessKind) &&
!Obj.mayReadMutableMembers(Info) &&
diagnoseUnreadableFields(Info, E, ObjType))
return handler.failed();
if (O->getArrayInitializedElts() > Index)
O = &O->getArrayInitializedElt(Index);
- else if (handler.AccessKind != AK_Read) {
+ else if (!isRead(handler.AccessKind)) {
expandArray(*O, Index);
O = &O->getArrayInitializedElt(Index);
} else
: O->getComplexFloatReal(), ObjType);
}
} else if (const FieldDecl *Field = getAsField(Sub.Entries[I])) {
- if (Field->isMutable() && handler.AccessKind == AK_Read &&
+ if (Field->isMutable() && isRead(handler.AccessKind) &&
!Obj.mayReadMutableMembers(Info)) {
Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1)
<< Field;
namespace {
struct ExtractSubobjectHandler {
EvalInfo &Info;
+ const Expr *E;
APValue &Result;
-
- static const AccessKinds AccessKind = AK_Read;
+ const AccessKinds AccessKind;
typedef bool result_type;
bool failed() { return false; }
bool found(APValue &Subobj, QualType SubobjType) {
Result = Subobj;
- return true;
+ if (AccessKind == AK_ReadObjectRepresentation)
+ return true;
+ return CheckFullyInitialized(Info, E->getExprLoc(), SubobjType, Result);
}
bool found(APSInt &Value, QualType SubobjType) {
Result = APValue(Value);
};
} // end anonymous namespace
-const AccessKinds ExtractSubobjectHandler::AccessKind;
-
/// Extract the designated sub-object of an rvalue.
static bool extractSubobject(EvalInfo &Info, const Expr *E,
const CompleteObject &Obj,
- const SubobjectDesignator &Sub,
- APValue &Result) {
- ExtractSubobjectHandler Handler = { Info, Result };
+ const SubobjectDesignator &Sub, APValue &Result,
+ AccessKinds AK = AK_Read) {
+ assert(AK == AK_Read || AK == AK_ReadObjectRepresentation);
+ ExtractSubobjectHandler Handler = {Info, E, Result, AK};
return findSubobject(Info, E, Obj, Sub, Handler);
}
/// case of a non-class type).
/// \param LVal - The glvalue on which we are attempting to perform this action.
/// \param RVal - The produced value will be placed here.
-static bool handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv,
- QualType Type,
- const LValue &LVal, APValue &RVal) {
+/// \param WantObjectRepresentation - If true, we're looking for the object
+/// representation rather than the value, and in particular,
+/// there is no requirement that the result be fully initialized.
+static bool
+handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, QualType Type,
+ const LValue &LVal, APValue &RVal,
+ bool WantObjectRepresentation = false) {
if (LVal.Designator.Invalid)
return false;
// Check for special cases where there is no existing APValue to look at.
const Expr *Base = LVal.Base.dyn_cast<const Expr*>();
+ AccessKinds AK =
+ WantObjectRepresentation ? AK_ReadObjectRepresentation : AK_Read;
+
if (Base && !LVal.getLValueCallIndex() && !Type.isVolatileQualified()) {
if (const CompoundLiteralExpr *CLE = dyn_cast<CompoundLiteralExpr>(Base)) {
// In C99, a CompoundLiteralExpr is an lvalue, and we defer evaluating the
if (!Evaluate(Lit, Info, CLE->getInitializer()))
return false;
CompleteObject LitObj(LVal.Base, &Lit, Base->getType());
- return extractSubobject(Info, Conv, LitObj, LVal.Designator, RVal);
+ return extractSubobject(Info, Conv, LitObj, LVal.Designator, RVal, AK);
} else if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) {
// Special-case character extraction so we don't have to construct an
// APValue for the whole string.
}
if (LVal.Designator.isOnePastTheEnd()) {
if (Info.getLangOpts().CPlusPlus11)
- Info.FFDiag(Conv, diag::note_constexpr_access_past_end) << AK_Read;
+ Info.FFDiag(Conv, diag::note_constexpr_access_past_end) << AK;
else
Info.FFDiag(Conv);
return false;
}
}
- CompleteObject Obj = findCompleteObject(Info, Conv, AK_Read, LVal, Type);
- return Obj && extractSubobject(Info, Conv, Obj, LVal.Designator, RVal);
+ CompleteObject Obj = findCompleteObject(Info, Conv, AK, LVal, Type);
+ return Obj && extractSubobject(Info, Conv, Obj, LVal.Designator, RVal, AK);
}
/// Perform an assignment of Val to LVal. Takes ownership of Val.
return CastToDerivedClass(Info, E, Result, TargetType, NewEntriesSize);
}
+/// Get the value to use for a default-initialized object of type T.
+static APValue getDefaultInitValue(QualType T) {
+ if (auto *RD = T->getAsCXXRecordDecl()) {
+ if (RD->isUnion())
+ return APValue((const FieldDecl*)nullptr);
+
+ APValue Struct(APValue::UninitStruct(), RD->getNumBases(),
+ std::distance(RD->field_begin(), RD->field_end()));
+
+ unsigned Index = 0;
+ for (CXXRecordDecl::base_class_const_iterator I = RD->bases_begin(),
+ End = RD->bases_end(); I != End; ++I, ++Index)
+ Struct.getStructBase(Index) = getDefaultInitValue(I->getType());
+
+ for (const auto *I : RD->fields()) {
+ if (I->isUnnamedBitfield())
+ continue;
+ Struct.getStructField(I->getFieldIndex()) =
+ getDefaultInitValue(I->getType());
+ }
+ return Struct;
+ }
+
+ if (auto *AT =
+ dyn_cast_or_null<ConstantArrayType>(T->getAsArrayTypeUnsafe())) {
+ APValue Array(APValue::UninitArray(), 0, AT->getSize().getZExtValue());
+ if (Array.hasArrayFiller())
+ Array.getArrayFiller() = getDefaultInitValue(AT->getElementType());
+ return Array;
+ }
+
+ return APValue::IndeterminateValue();
+}
+
namespace {
enum EvalStmtResult {
/// Evaluation failed.
const Expr *InitE = VD->getInit();
if (!InitE) {
- Info.FFDiag(VD->getBeginLoc(), diag::note_constexpr_uninitialized)
- << false << VD->getType();
- Val = APValue();
- return false;
+ Val = getDefaultInitValue(VD->getType());
+ return true;
}
if (InitE->isValueDependent())
break;
}
- case Stmt::DeclStmtClass:
- // FIXME: If the variable has initialization that can't be jumped over,
- // bail out of any immediately-surrounding compound-statement too.
+ case Stmt::DeclStmtClass: {
+ // Start the lifetime of any uninitialized variables we encounter. They
+ // might be used by the selected branch of the switch.
+ const DeclStmt *DS = cast<DeclStmt>(S);
+ for (const auto *D : DS->decls()) {
+ if (const auto *VD = dyn_cast<VarDecl>(D)) {
+ if (VD->hasLocalStorage() && !VD->getInit())
+ if (!EvaluateVarDecl(Info, VD))
+ return ESR_Failed;
+ // FIXME: If the variable has initialization that can't be jumped
+ // over, bail out of any immediately-surrounding compound-statement
+ // too. There can't be any case labels here.
+ }
+ }
+ return ESR_CaseNotFound;
+ }
+
default:
return ESR_CaseNotFound;
}
case Stmt::DeclStmtClass: {
const DeclStmt *DS = cast<DeclStmt>(S);
- for (const auto *DclIt : DS->decls()) {
+ for (const auto *D : DS->decls()) {
// 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.noteFailure())
+ if (!EvaluateDecl(Info, D) && !Info.noteFailure())
return ESR_Failed;
}
return ESR_Succeeded;
static const AccessKinds AccessKind = AK_Assign;
- APValue getDefaultInitValue(QualType SubobjType) {
- if (auto *RD = SubobjType->getAsCXXRecordDecl()) {
- if (RD->isUnion())
- return APValue((const FieldDecl*)nullptr);
-
- APValue Struct(APValue::UninitStruct(), RD->getNumBases(),
- std::distance(RD->field_begin(), RD->field_end()));
-
- unsigned Index = 0;
- for (CXXRecordDecl::base_class_const_iterator I = RD->bases_begin(),
- End = RD->bases_end(); I != End; ++I, ++Index)
- Struct.getStructBase(Index) = getDefaultInitValue(I->getType());
-
- for (const auto *I : RD->fields()) {
- if (I->isUnnamedBitfield())
- continue;
- Struct.getStructField(I->getFieldIndex()) =
- getDefaultInitValue(I->getType());
- }
- return Struct;
- }
-
- if (auto *AT = dyn_cast_or_null<ConstantArrayType>(
- SubobjType->getAsArrayTypeUnsafe())) {
- APValue Array(APValue::UninitArray(), 0, AT->getSize().getZExtValue());
- if (Array.hasArrayFiller())
- Array.getArrayFiller() = getDefaultInitValue(AT->getElementType());
- return Array;
- }
-
- return APValue::IndeterminateValue();
- }
-
typedef bool result_type;
bool failed() { return false; }
bool found(APValue &Subobj, QualType SubobjType) {
LValue RHS;
RHS.setFrom(Info.Ctx, ArgValues[0]);
APValue RHSValue;
- if (!handleLValueToRValueConversion(Info, Args[0], Args[0]->getType(),
- RHS, RHSValue))
+ if (!handleLValueToRValueConversion(Info, Args[0], Args[0]->getType(), RHS,
+ RHSValue, MD->getParent()->isUnion()))
return false;
if (Info.getLangOpts().CPlusPlus2a && MD->isTrivial() &&
!HandleUnionActiveMemberChange(Info, Args[0], *This))
RHS.setFrom(Info.Ctx, ArgValues[0]);
return handleLValueToRValueConversion(
Info, E, Definition->getParamDecl(0)->getType().getNonReferenceType(),
- RHS, Result);
+ RHS, Result, Definition->getParent()->isUnion());
}
// Reserve space for the struct members.
#ifndef NDEBUG
CXXRecordDecl::base_class_const_iterator BaseIt = RD->bases_begin();
#endif
+ CXXRecordDecl::field_iterator FieldIt = RD->field_begin();
+ auto SkipToField = [&](FieldDecl *FD, bool Indirect) {
+ // We might be initializing the same field again if this is an indirect
+ // field initialization.
+ if (FieldIt == RD->field_end() ||
+ FieldIt->getFieldIndex() > FD->getFieldIndex()) {
+ assert(Indirect && "fields out of order?");
+ return;
+ }
+
+ // Default-initialize any fields with no explicit initializer.
+ for (; !declaresSameEntity(*FieldIt, FD); ++FieldIt) {
+ assert(FieldIt != RD->field_end() && "missing field?");
+ if (!FieldIt->isUnnamedBitfield())
+ Result.getStructField(FieldIt->getFieldIndex()) =
+ getDefaultInitValue(FieldIt->getType());
+ }
+ ++FieldIt;
+ };
for (const auto *I : Definition->inits()) {
LValue Subobject = This;
LValue SubobjectParent = This;
Result = APValue(FD);
Value = &Result.getUnionValue();
} else {
+ SkipToField(FD, false);
Value = &Result.getStructField(FD->getFieldIndex());
}
} else if (IndirectFieldDecl *IFD = I->getIndirectMember()) {
if (CD->isUnion())
*Value = APValue(FD);
else
- *Value = APValue(APValue::UninitStruct(), CD->getNumBases(),
- std::distance(CD->field_begin(), CD->field_end()));
+ // FIXME: This immediately starts the lifetime of all members of an
+ // anonymous struct. It would be preferable to strictly start member
+ // lifetime in initialization order.
+ *Value = getDefaultInitValue(Info.Ctx.getRecordType(CD));
}
// Store Subobject as its parent before updating it for the last element
// in the chain.
return false;
if (CD->isUnion())
Value = &Value->getUnionValue();
- else
+ else {
+ if (C == IndirectFieldChain.front() && !RD->isUnion())
+ SkipToField(FD, true);
Value = &Value->getStructField(FD->getFieldIndex());
+ }
}
} else {
llvm_unreachable("unknown base initializer kind");
EvalObj.finishedConstructingBases();
}
+ // Default-initialize any remaining fields.
+ if (!RD->isUnion()) {
+ for (; FieldIt != RD->field_end(); ++FieldIt) {
+ if (!FieldIt->isUnnamedBitfield())
+ Result.getStructField(FieldIt->getFieldIndex()) =
+ getDefaultInitValue(FieldIt->getType());
+ }
+ }
+
return Success &&
EvaluateStmt(Ret, Info, Definition->getBody()) != ESR_Failed;
}
LValue SourceLValue;
APValue SourceRValue;
SourceLValue.setFrom(Info.Ctx, SourceValue);
- if (!handleLValueToRValueConversion(Info, BCE,
- BCE->getSubExpr()->getType().withConst(),
- SourceLValue, SourceRValue))
+ if (!handleLValueToRValueConversion(
+ Info, BCE, BCE->getSubExpr()->getType().withConst(), SourceLValue,
+ SourceRValue, /*WantObjectRepresentation=*/true))
return false;
// Read out SourceValue into a char buffer.
while (true) {
APValue Val;
+ // FIXME: Set WantObjectRepresentation to true if we're copying a
+ // char-like type?
if (!handleLValueToRValueConversion(Info, E, T, Src, Val) ||
!handleAssignment(Info, E, Dest, T, Val))
return false;
if (Result.hasValue())
return true;
- // We can get here in two different ways:
- // 1) We're performing value-initialization, and should zero-initialize
- // the object, or
- // 2) We're performing default-initialization of an object with a trivial
- // constexpr default constructor, in which case we should start the
- // lifetimes of all the base subobjects (there can be no data member
- // subobjects in this case) per [basic.life]p1.
- // Either way, ZeroInitialization is appropriate.
- return ZeroInitialization(E, T);
+ if (ZeroInit)
+ return ZeroInitialization(E, T);
+
+ Result = getDefaultInitValue(T);
+ return true;
}
const FunctionDecl *Definition = nullptr;
/// same set of semantic restrictions.
enum AccessKinds {
AK_Read,
+ AK_ReadObjectRepresentation,
AK_Assign,
AK_Increment,
AK_Decrement,
case Decl::Decomposition: {
// C++1y [dcl.constexpr]p3 allows anything except:
// a definition of a variable of non-literal type or of static or
- // thread storage duration or for which no initialization is performed.
+ // thread storage duration or [before C++2a] for which no
+ // initialization is performed.
const auto *VD = cast<VarDecl>(DclIt);
if (VD->isThisDeclarationADefinition()) {
if (VD->isStaticLocal()) {
if (!VD->getType()->isDependentType() &&
!VD->hasInit() && !VD->isCXXForRangeDecl()) {
if (Kind == Sema::CheckConstexprKind::Diagnose) {
- SemaRef.Diag(VD->getLocation(),
- diag::err_constexpr_local_var_no_init)
- << isa<CXXConstructorDecl>(Dcl);
+ SemaRef.Diag(
+ VD->getLocation(),
+ SemaRef.getLangOpts().CPlusPlus2a
+ ? diag::warn_cxx17_compat_constexpr_local_var_no_init
+ : diag::ext_constexpr_local_var_no_init)
+ << isa<CXXConstructorDecl>(Dcl);
+ } else if (!SemaRef.getLangOpts().CPlusPlus2a) {
+ return false;
}
- return false;
+ continue;
}
}
if (Kind == Sema::CheckConstexprKind::Diagnose) {
llvm::SmallSet<Decl*, 16> &Inits,
bool &Diagnosed,
Sema::CheckConstexprKind Kind) {
+ // In C++20 onwards, there's nothing to check for validity.
+ if (Kind == Sema::CheckConstexprKind::CheckValid &&
+ SemaRef.getLangOpts().CPlusPlus2a)
+ return true;
+
if (Field->isInvalidDecl())
return true;
if (!Inits.count(Field)) {
if (Kind == Sema::CheckConstexprKind::Diagnose) {
if (!Diagnosed) {
- SemaRef.Diag(Dcl->getLocation(), diag::err_constexpr_ctor_missing_init);
+ SemaRef.Diag(Dcl->getLocation(),
+ SemaRef.getLangOpts().CPlusPlus2a
+ ? diag::warn_cxx17_compat_constexpr_ctor_missing_init
+ : diag::ext_constexpr_ctor_missing_init);
Diagnosed = true;
}
SemaRef.Diag(Field->getLocation(),
diag::note_constexpr_ctor_missing_init);
- } else {
+ } else if (!SemaRef.getLangOpts().CPlusPlus2a) {
return false;
}
} else if (Field->isAnonymousStructOrUnion()) {
if (RD->isUnion()) {
if (Constructor->getNumCtorInitializers() == 0 &&
RD->hasVariantMembers()) {
- if (Kind == Sema::CheckConstexprKind::Diagnose)
- SemaRef.Diag(Dcl->getLocation(),
- diag::err_constexpr_union_ctor_no_init);
- return false;
+ if (Kind == Sema::CheckConstexprKind::Diagnose) {
+ SemaRef.Diag(
+ Dcl->getLocation(),
+ SemaRef.getLangOpts().CPlusPlus2a
+ ? diag::warn_cxx17_compat_constexpr_union_ctor_no_init
+ : diag::ext_constexpr_union_ctor_no_init);
+ } else if (!SemaRef.getLangOpts().CPlusPlus2a) {
+ return false;
+ }
}
} else if (!Constructor->isDependentContext() &&
!Constructor->isDelegatingConstructor()) {
}
constexpr int DisallowedStmtsCXX1Y_7() {
// - a definition of a variable for which no initialization is performed
- int n; // expected-error {{variables defined in a constexpr function must be initialized}}
+ int n;
+#ifndef CXX2A
+ // expected-error@-2 {{uninitialized variable in a constexpr function}}
+#endif
return 0;
}
return value;
}
constexpr int uninit() {
- int a; // expected-error {{must be initialized}}
+ int a;
+#ifndef CXX2A
+ // expected-error@-2 {{uninitialized}}
+#endif
return a;
}
constexpr int prev(int x) {
// - every non-static data member and base class sub-object shall be initialized
struct W {
- int n; // expected-note {{member not initialized by constructor}}
- constexpr W() {} // expected-error {{constexpr constructor must initialize all members}}
+ int n;
+ constexpr W() {}
+#ifndef CXX2A
+ // expected-error@-2 {{constexpr constructor that does not initialize all members}}
+ // expected-note@-4 {{member not initialized by constructor}}
+#endif
};
struct AnonMembers {
- int a; // expected-note {{member not initialized by constructor}}
- union { // expected-note 2{{member not initialized by constructor}}
+ int a; // expected-note 0-1{{member not initialized by constructor}}
+ union { // expected-note 0-2{{member not initialized by constructor}}
char b;
struct {
double c;
- long d; // expected-note {{member not initialized by constructor}}
+ long d; // expected-note 0-1{{member not initialized by constructor}}
};
union {
char e;
void *f;
};
};
- struct { // expected-note {{member not initialized by constructor}}
+ struct { // expected-note 0-1{{member not initialized by constructor}}
long long g;
struct {
- int h; // expected-note {{member not initialized by constructor}}
- double i; // expected-note {{member not initialized by constructor}}
+ int h; // expected-note 0-1{{member not initialized by constructor}}
+ double i; // expected-note 0-1{{member not initialized by constructor}}
};
- union { // expected-note 2{{member not initialized by constructor}}
+ union { // expected-note 0-2{{member not initialized by constructor}}
char *j;
AnonMembers *k;
};
constexpr AnonMembers(int(&)[1]) : a(), b(), g(), h(), i(), j() {} // ok
// missing d, i, j/k union
- constexpr AnonMembers(int(&)[2]) : a(), c(), g(), h() {} // expected-error {{constexpr constructor must initialize all members}}
+ constexpr AnonMembers(int(&)[2]) : a(), c(), g(), h() {}
+#ifndef CXX2A
+ // expected-error@-2 {{constexpr constructor that does not initialize all members}}
+#endif
constexpr AnonMembers(int(&)[3]) : a(), e(), g(), h(), i(), k() {} // ok
// missing h, j/k union
- constexpr AnonMembers(int(&)[4]) : a(), c(), d(), g(), i() {} // expected-error {{constexpr constructor must initialize all members}}
+ constexpr AnonMembers(int(&)[4]) : a(), c(), d(), g(), i() {}
+#ifndef CXX2A
+ // expected-error@-2 {{constexpr constructor that does not initialize all members}}
+#endif
// missing b/c/d/e/f union
- constexpr AnonMembers(int(&)[5]) : a(), g(), h(), i(), k() {} // expected-error {{constexpr constructor must initialize all members}}
+ constexpr AnonMembers(int(&)[5]) : a(), g(), h(), i(), k() {}
+#ifndef CXX2A
+ // expected-error@-2 {{constexpr constructor that does not initialize all members}}
+#endif
// missing a, b/c/d/e/f union, g/h/i/j/k struct
- constexpr AnonMembers(int(&)[6]) {} // expected-error {{constexpr constructor must initialize all members}}
+ constexpr AnonMembers(int(&)[6]) {}
+#ifndef CXX2A
+ // expected-error@-2 {{constexpr constructor that does not initialize all members}}
+#endif
};
union Empty {
constexpr X(int c) : a(c) {} // ok, b initialized by 2 * c + 1
};
-union XU1 { int a; constexpr XU1() = default; }; // expected-error{{not constexpr}}
+union XU1 { int a; constexpr XU1() = default; };
+#ifndef CXX2A
+// expected-error@-2{{not constexpr}}
+#endif
union XU2 { int a = 1; constexpr XU2() = default; };
struct XU3 {
union {
int a;
};
- constexpr XU3() = default; // expected-error{{not constexpr}}
+ constexpr XU3() = default;
+#ifndef CXX2A
+ // expected-error@-2{{not constexpr}}
+#endif
};
struct XU4 {
union {
// RUN: %clang_cc1 -std=c++98 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
// RUN: %clang_cc1 -std=c++11 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
// RUN: %clang_cc1 -std=c++14 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++1z %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++17 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++2a %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
#if __cplusplus < 201103L
// expected-no-diagnostics
}
union A {};
- union B { int n; }; // expected-note +{{here}}
+ union B { int n; }; // expected-note 0+{{here}}
union C { int n = 0; };
struct D { union {}; }; // expected-error {{does not declare anything}}
- struct E { union { int n; }; }; // expected-note +{{here}}
+ struct E { union { int n; }; }; // expected-note 0+{{here}}
struct F { union { int n = 0; }; };
struct X {
friend constexpr A::A() noexcept;
- friend constexpr B::B() noexcept; // expected-error {{follows non-constexpr declaration}}
+ friend constexpr B::B() noexcept;
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{follows non-constexpr declaration}}
+#endif
friend constexpr C::C() noexcept;
friend constexpr D::D() noexcept;
- friend constexpr E::E() noexcept; // expected-error {{follows non-constexpr declaration}}
+ friend constexpr E::E() noexcept;
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{follows non-constexpr declaration}}
+#endif
friend constexpr F::F() noexcept;
};
namespace Defaulted {
union A { constexpr A() = default; };
- union B { int n; constexpr B() = default; }; // expected-error {{not constexpr}}
+ union B { int n; constexpr B() = default; };
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{not constexpr}}
+#endif
union C { int n = 0; constexpr C() = default; };
struct D { union {}; constexpr D() = default; }; // expected-error {{does not declare anything}}
- struct E { union { int n; }; constexpr E() = default; }; // expected-error {{not constexpr}}
+ struct E { union { int n; }; constexpr E() = default; };
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{not constexpr}}
+#endif
struct F { union { int n = 0; }; constexpr F() = default; };
- struct G { union { int n = 0; }; union { int m; }; constexpr G() = default; }; // expected-error {{not constexpr}}
+ struct G { union { int n = 0; }; union { int m; }; constexpr G() = default; };
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{not constexpr}}
+#endif
struct H {
union {
int n = 0;
};
- union { // expected-note 2{{member not initialized}}
+ union { // expected-note 0-2{{member not initialized}}
int m;
};
- constexpr H() {} // expected-error {{must initialize all members}}
+ constexpr H() {}
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{initialize all members}}
+#endif
constexpr H(bool) : m(1) {}
- constexpr H(char) : n(1) {} // expected-error {{must initialize all members}}
+ constexpr H(char) : n(1) {}
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{initialize all members}}
+#endif
constexpr H(double) : m(1), n(1) {}
};
}
#if __cplusplus > 201103L
template<typename T> constexpr bool check() {
- T t; // expected-note-re 2{{non-constexpr constructor '{{[BE]}}'}}
+ T t;
+#if __cplusplus <= 201703L
+ // expected-note-re@-2 2{{non-constexpr constructor '{{[BE]}}'}}
+#endif
return true;
}
static_assert(check<A>(), "");
- static_assert(check<B>(), ""); // expected-error {{constant}} expected-note {{in call}}
+ static_assert(check<B>(), "");
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{constant}} expected-note@-2 {{in call}}
+#endif
static_assert(check<C>(), "");
static_assert(check<D>(), "");
- static_assert(check<E>(), ""); // expected-error {{constant}} expected-note {{in call}}
+ static_assert(check<E>(), "");
+#if __cplusplus <= 201703L
+ // expected-error@-2 {{constant}} expected-note@-2 {{in call}}
+#endif
static_assert(check<F>(), "");
#endif
};
constexpr X() noexcept {};
protected:
- E val{0}; // expected-error {{cannot initialize a member subobject of type 'ConstexprConstructorRecovery::X::E' with an rvalue of type 'int'}}
+ E val{0}; // expected-error {{cannot initialize a member subobject of type 'ConstexprConstructorRecovery::X::E' with an rvalue of type 'int'}} expected-note {{here}}
};
- constexpr X x{};
+ // FIXME: We should avoid issuing this follow-on diagnostic.
+ constexpr X x{}; // expected-error {{constant expression}} expected-note {{not initialized}}
}
namespace Lifetime {
// here is bogus (we discard the k(k) initializer because the parameter 'k'
// has been marked invalid).
struct B { // expected-note 2{{candidate}}
- constexpr B( // expected-error {{must initialize all members}} expected-note {{candidate}}
+ constexpr B( // expected-warning {{initialize all members}} expected-note {{candidate}}
int k = X<B().k>::n) : // expected-error {{no matching constructor}}
k(k) {}
int k; // expected-note {{not initialized}}
}
static_assert(f(1) == 1, ""); // expected-error {{constant expression}} expected-note {{in call}}
- constexpr int g() { // expected-error {{never produces a constant}}
- return ({ int n; n; }); // expected-note {{object of type 'int' is not initialized}}
+ constexpr int g() {
+ return ({ int n; n; }); // expected-note {{read of uninitialized object}}
}
+ static_assert(g() == 0, ""); // expected-error {{constant expression}} expected-note {{in call}}
// FIXME: We should handle the void statement expression case.
constexpr int h() { // expected-error {{never produces a constant}}
namespace Union {
struct Base {
- int y; // expected-note {{here}}
+ int y; // expected-note 2{{here}}
};
struct A : Base {
int x;
constexpr A return_uninit_struct() {
B b = {.b = 1};
b.a.x = 2;
- return b.a;
+ return b.a; // expected-note {{in call to 'A(b.a)'}} expected-note {{subobject of type 'int' is not initialized}}
}
- // FIXME: It's unclear that this should be valid. Copying a B involves
- // copying the object representation of the union, but copying an A invokes a
- // copy constructor that copies the object elementwise, and reading from
- // b.a.y is undefined.
- static_assert(return_uninit_struct().x == 2);
+ // Note that this is rejected even though return_uninit() is accepted, and
+ // return_uninit() copies the same stuff wrapped in a union.
+ //
+ // Copying a B involves copying the object representation of the union, but
+ // copying an A invokes a copy constructor that copies the object
+ // elementwise, and reading from b.a.y is undefined.
+ static_assert(return_uninit_struct().x == 2); // expected-error {{constant expression}} expected-note {{in call}}
constexpr B return_init_all() {
B b = {.b = 1};
b.a.x = 2;
static_assert(-3 >> 1 == -2);
static_assert(-4 >> 1 == -2);
}
+
+namespace Uninit {
+ constexpr int f(bool init) {
+ int a;
+ if (init)
+ a = 1;
+ return a; // expected-note {{read of uninitialized object}}
+ }
+ static_assert(f(true) == 1);
+ static_assert(f(false) == 1); // expected-error {{constant expression}} expected-note {{in call}}
+
+ struct X {
+ int n; // expected-note {{declared here}}
+ constexpr X(bool init) {
+ if (init) n = 123;
+ }
+ };
+ constinit X x1(true);
+ constinit X x2(false); // expected-error {{constant initializer}} expected-note {{constinit}} expected-note {{subobject of type 'int' is not initialized}}
+
+ struct Y {
+ struct Z { int n; }; // expected-note {{here}}
+ Z z1;
+ Z z2;
+ Z z3;
+ // OK: the lifetime of z1 (and its members) start before the initializer of
+ // z2 runs.
+ constexpr Y() : z2{ (z1.n = 1, z1.n + 1) } { z3.n = 3; }
+ // Not OK: z3 is not in its lifetime when the initializer of z2 runs.
+ constexpr Y(int) : z2{
+ (z3.n = 1, // expected-note {{assignment to object outside its lifetime}}
+ z3.n + 1) // expected-warning {{uninitialized}}
+ } { z1.n = 3; }
+ constexpr Y(int, int) : z2{} {}
+ };
+ // FIXME: This is working around clang not implementing DR2026. With that
+ // fixed, we should be able to test this without the injected copy.
+ constexpr Y copy(Y y) { return y; } // expected-note {{in call to 'Y(y)'}} expected-note {{subobject of type 'int' is not initialized}}
+ constexpr Y y1 = copy(Y());
+ static_assert(y1.z1.n == 1 && y1.z2.n == 2 && y1.z3.n == 3);
+
+ constexpr Y y2 = copy(Y(0)); // expected-error {{constant expression}} expected-note {{in call}}
+
+ static_assert(Y(0,0).z2.n == 0);
+ static_assert(Y(0,0).z1.n == 0); // expected-error {{constant expression}} expected-note {{read of uninitialized object}}
+ static_assert(Y(0,0).z3.n == 0); // expected-error {{constant expression}} expected-note {{read of uninitialized object}}
+
+ static_assert(copy(Y(0,0)).z2.n == 0); // expected-error {{constant expression}} expected-note {{in call}}
+
+ constexpr unsigned char not_even_unsigned_char() {
+ unsigned char c;
+ return c; // expected-note {{read of uninitialized object}}
+ }
+ constexpr unsigned char x = not_even_unsigned_char(); // expected-error {{constant expression}} expected-note {{in call}}
+
+ constexpr int switch_var(int n) {
+ switch (n) {
+ case 1:
+ int a;
+ a = n;
+ return a;
+
+ case 2:
+ a = n;
+ return a;
+ }
+ }
+ constexpr int s1 = switch_var(1);
+ constexpr int s2 = switch_var(2);
+ static_assert(s1 == 1 && s2 == 2);
+}
</tr>
<tr> <!-- from Cologne -->
<td><a href="http://wg21.link/p1331r2">P1331R2</a></td>
- <td class="none" align="center">No</td>
+ <td rowspan="2" class="svn" align="center">SVN</td>
</tr>
<tr>
<td><a href="http://wg21.link/p1668r1">P1668R1</a></td>
- <td class="svn" align="center">Clang 9</td>
</tr>
<tr>
<td><a href="http://wg21.link/p0784r7">P0784R7</a></td>