[c++20] P1064R0: Allow virtual function calls in constant expression

evaluation.

llvm-svn: 360559
This commit is contained in:
Richard Smith 2019-05-13 07:42:10 +00:00
parent f3be557159
commit 5c5be6b2f7
14 changed files with 413 additions and 51 deletions

View File

@ -2298,6 +2298,17 @@ public:
->getCorrespondingMethodInClass(RD, MayBeBase);
}
/// Find if \p RD declares a function that overrides this function, and if so,
/// return it. Does not search base classes.
CXXMethodDecl *getCorrespondingMethodDeclaredInClass(const CXXRecordDecl *RD,
bool MayBeBase = false);
const CXXMethodDecl *
getCorrespondingMethodDeclaredInClass(const CXXRecordDecl *RD,
bool MayBeBase = false) const {
return const_cast<CXXMethodDecl *>(this)
->getCorrespondingMethodDeclaredInClass(RD, MayBeBase);
}
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) {

View File

@ -32,6 +32,8 @@ def note_constexpr_no_return : Note<
"control reached end of constexpr function">;
def note_constexpr_virtual_call : Note<
"cannot evaluate call to virtual function in a constant expression">;
def note_constexpr_pure_virtual_call : Note<
"pure virtual function %q0 called">;
def note_constexpr_virtual_base : Note<
"cannot construct object of type %0 with virtual base class "
"in a constant expression">;

View File

@ -2314,6 +2314,9 @@ def err_constexpr_redecl_mismatch : Error<
"%select{non-constexpr declaration of %0 follows constexpr declaration"
"|constexpr declaration of %0 follows non-constexpr declaration}1">;
def err_constexpr_virtual : Error<"virtual function cannot be constexpr">;
def warn_cxx17_compat_constexpr_virtual : Warning<
"virtual constexpr functions are incompatible with "
"C++ standards before C++2a">, InGroup<CXXPre2aCompat>, DefaultIgnore;
def err_constexpr_virtual_base : Error<
"constexpr %select{member function|constructor}0 not allowed in "
"%select{struct|interface|class}1 with virtual base "

View File

@ -5984,8 +5984,8 @@ public:
/// MarkVirtualMembersReferenced - Will mark all members of the given
/// CXXRecordDecl referenced.
void MarkVirtualMembersReferenced(SourceLocation Loc,
const CXXRecordDecl *RD);
void MarkVirtualMembersReferenced(SourceLocation Loc, const CXXRecordDecl *RD,
bool ConstexprOnly = false);
/// Define all of the vtables that have been used in this
/// translation unit and reference any virtual members used by those

View File

@ -1946,8 +1946,8 @@ static bool recursivelyOverrides(const CXXMethodDecl *DerivedMD,
}
CXXMethodDecl *
CXXMethodDecl::getCorrespondingMethodInClass(const CXXRecordDecl *RD,
bool MayBeBase) {
CXXMethodDecl::getCorrespondingMethodDeclaredInClass(const CXXRecordDecl *RD,
bool MayBeBase) {
if (this->getParent()->getCanonicalDecl() == RD->getCanonicalDecl())
return this;
@ -1973,6 +1973,15 @@ CXXMethodDecl::getCorrespondingMethodInClass(const CXXRecordDecl *RD,
return MD;
}
return nullptr;
}
CXXMethodDecl *
CXXMethodDecl::getCorrespondingMethodInClass(const CXXRecordDecl *RD,
bool MayBeBase) {
if (auto *MD = getCorrespondingMethodDeclaredInClass(RD, MayBeBase))
return MD;
for (const auto &I : RD->bases()) {
const RecordType *RT = I.getType()->getAs<RecordType>();
if (!RT)

View File

@ -37,6 +37,7 @@
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/ASTLambda.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/Expr.h"
#include "clang/AST/OSLog.h"
#include "clang/AST/RecordLayout.h"
@ -2485,6 +2486,21 @@ static bool HandleLValueBasePath(EvalInfo &Info, const CastExpr *E,
return true;
}
/// Cast an lvalue referring to a derived class to a known base subobject.
static bool CastToBaseClass(EvalInfo &Info, const Expr *E, LValue &Result,
const CXXRecordDecl *DerivedRD,
const CXXRecordDecl *BaseRD) {
CXXBasePaths Paths(/*FindAmbiguities=*/false,
/*RecordPaths=*/true, /*DetectVirtual=*/false);
if (!DerivedRD->isDerivedFrom(BaseRD, Paths))
llvm_unreachable("Class must be derived from the passed in base class!");
for (CXXBasePathElement &Elem : Paths.front())
if (!HandleLValueBase(Info, E, Result, Elem.Class, Elem.Base))
return false;
return true;
}
/// Update LVal to refer to the given field, which must be a member of the type
/// currently described by LVal.
static bool HandleLValueMember(EvalInfo &Info, const Expr *E, LValue &LVal,
@ -4461,16 +4477,19 @@ static bool CheckConstexprFunction(EvalInfo &Info, SourceLocation CallLoc,
}
// DR1872: An instantiated virtual constexpr function can't be called in a
// constant expression.
if (isa<CXXMethodDecl>(Declaration) &&
cast<CXXMethodDecl>(Declaration)->isVirtual()) {
Info.FFDiag(CallLoc, diag::note_constexpr_virtual_call);
// constant expression (prior to C++20). We can still constant-fold such a
// call.
if (!Info.Ctx.getLangOpts().CPlusPlus2a && isa<CXXMethodDecl>(Declaration) &&
cast<CXXMethodDecl>(Declaration)->isVirtual())
Info.CCEDiag(CallLoc, diag::note_constexpr_virtual_call);
if (Definition && Definition->isInvalidDecl()) {
Info.FFDiag(CallLoc, diag::note_invalid_subexpr_in_const_expr);
return false;
}
// Can we evaluate this function call?
if (Definition && Definition->isConstexpr() &&
!Definition->isInvalidDecl() && Body)
if (Definition && Definition->isConstexpr() && Body)
return true;
if (Info.getLangOpts().CPlusPlus11) {
@ -4543,6 +4562,153 @@ static bool checkMemberCallThisPointer(EvalInfo &Info, const Expr *E,
return Obj && findSubobject(Info, E, Obj, This.Designator, Handler);
}
struct DynamicType {
/// The dynamic class type of the object.
const CXXRecordDecl *Type;
/// The corresponding path length in the lvalue.
unsigned PathLength;
};
static const CXXRecordDecl *getBaseClassType(SubobjectDesignator &Designator,
unsigned PathLength) {
assert(PathLength >= Designator.MostDerivedPathLength && PathLength <=
Designator.Entries.size() && "invalid path length");
return (PathLength == Designator.MostDerivedPathLength)
? Designator.MostDerivedType->getAsCXXRecordDecl()
: getAsBaseClass(Designator.Entries[PathLength - 1]);
}
/// Determine the dynamic type of an object.
static Optional<DynamicType> ComputeDynamicType(EvalInfo &Info, LValue &This) {
// If we don't have an lvalue denoting an object of class type, there is no
// meaningful dynamic type. (We consider objects of non-class type to have no
// dynamic type.)
if (This.Designator.IsOnePastTheEnd || This.Designator.Invalid ||
!This.Designator.MostDerivedType->getAsCXXRecordDecl())
return None;
// FIXME: For very deep class hierarchies, it might be beneficial to use a
// binary search here instead. But the overwhelmingly common case is that
// we're not in the middle of a constructor, so it probably doesn't matter
// in practice.
ArrayRef<APValue::LValuePathEntry> Path = This.Designator.Entries;
for (unsigned PathLength = This.Designator.MostDerivedPathLength;
PathLength <= Path.size(); ++PathLength) {
switch (Info.isEvaluatingConstructor(This.getLValueBase(),
Path.slice(0, PathLength))) {
case ConstructionPhase::Bases:
// We're constructing a base class. This is not the dynamic type.
break;
case ConstructionPhase::None:
case ConstructionPhase::AfterBases:
// We've finished constructing the base classes, so this is the dynamic
// type.
return DynamicType{getBaseClassType(This.Designator, PathLength),
PathLength};
}
}
// CWG issue 1517: we're constructing a base class of the object described by
// 'This', so that object has not yet begun its period of construction and
// any polymorphic operation on it results in undefined behavior.
return None;
}
/// Perform virtual dispatch.
static const CXXMethodDecl *HandleVirtualDispatch(
EvalInfo &Info, const Expr *E, LValue &This, const CXXMethodDecl *Found,
llvm::SmallVectorImpl<QualType> &CovariantAdjustmentPath) {
Optional<DynamicType> DynType = ComputeDynamicType(Info, This);
if (!DynType) {
Info.FFDiag(E);
return nullptr;
}
// Find the final overrider. It must be declared in one of the classes on the
// path from the dynamic type to the static type.
// FIXME: If we ever allow literal types to have virtual base classes, that
// won't be true.
const CXXMethodDecl *Callee = Found;
unsigned PathLength = DynType->PathLength;
for (/**/; PathLength <= This.Designator.Entries.size(); ++PathLength) {
const CXXRecordDecl *Class = getBaseClassType(This.Designator, PathLength);
assert(!Class->getNumVBases() &&
"can't handle virtual calls with virtual bases");
const CXXMethodDecl *Overrider =
Found->getCorrespondingMethodDeclaredInClass(Class, false);
if (Overrider) {
Callee = Overrider;
break;
}
}
// C++2a [class.abstract]p6:
// the effect of making a virtual call to a pure virtual function [...] is
// undefined
if (Callee->isPure()) {
Info.FFDiag(E, diag::note_constexpr_pure_virtual_call, 1) << Callee;
Info.Note(Callee->getLocation(), diag::note_declared_at);
return nullptr;
}
// If necessary, walk the rest of the path to determine the sequence of
// covariant adjustment steps to apply.
if (!Info.Ctx.hasSameUnqualifiedType(Callee->getReturnType(),
Found->getReturnType())) {
CovariantAdjustmentPath.push_back(Callee->getReturnType());
for (unsigned CovariantPathLength = PathLength + 1;
CovariantPathLength != This.Designator.Entries.size();
++CovariantPathLength) {
const CXXRecordDecl *NextClass =
getBaseClassType(This.Designator, CovariantPathLength);
const CXXMethodDecl *Next =
Found->getCorrespondingMethodDeclaredInClass(NextClass, false);
if (Next && !Info.Ctx.hasSameUnqualifiedType(
Next->getReturnType(), CovariantAdjustmentPath.back()))
CovariantAdjustmentPath.push_back(Next->getReturnType());
}
if (!Info.Ctx.hasSameUnqualifiedType(Found->getReturnType(),
CovariantAdjustmentPath.back()))
CovariantAdjustmentPath.push_back(Found->getReturnType());
}
// Perform 'this' adjustment.
if (!CastToDerivedClass(Info, E, This, Callee->getParent(), PathLength))
return nullptr;
return Callee;
}
/// Perform the adjustment from a value returned by a virtual function to
/// a value of the statically expected type, which may be a pointer or
/// reference to a base class of the returned type.
static bool HandleCovariantReturnAdjustment(EvalInfo &Info, const Expr *E,
APValue &Result,
ArrayRef<QualType> Path) {
assert(Result.isLValue() &&
"unexpected kind of APValue for covariant return");
if (Result.isNullPointer())
return true;
LValue LVal;
LVal.setFrom(Info.Ctx, Result);
const CXXRecordDecl *OldClass = Path[0]->getPointeeCXXRecordDecl();
for (unsigned I = 1; I != Path.size(); ++I) {
const CXXRecordDecl *NewClass = Path[I]->getPointeeCXXRecordDecl();
assert(OldClass && NewClass && "unexpected kind of covariant return");
if (OldClass != NewClass &&
!CastToBaseClass(Info, E, LVal, OldClass, NewClass))
return false;
OldClass = NewClass;
}
LVal.moveInto(Result);
return true;
}
/// Determine if a class has any fields that might need to be copied by a
/// trivial copy or move operation.
static bool hasFields(const CXXRecordDecl *RD) {
@ -4732,11 +4898,6 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This,
BaseType->getAsCXXRecordDecl(), &Layout))
return false;
Value = &Result.getStructBase(BasesSeen++);
// This is the point at which the dynamic type of the object becomes this
// class type.
if (BasesSeen == RD->getNumBases())
EvalObj.finishedConstructingBases();
} else if ((FD = I->getMember())) {
if (!HandleLValueMember(Info, I->getInit(), Subobject, FD, &Layout))
return false;
@ -4797,6 +4958,11 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This,
return false;
Success = false;
}
// This is the point at which the dynamic type of the object becomes this
// class type.
if (I->isBaseInitializer() && BasesSeen == RD->getNumBases())
EvalObj.finishedConstructingBases();
}
return Success &&
@ -5037,27 +5203,30 @@ public:
const FunctionDecl *FD = nullptr;
LValue *This = nullptr, ThisVal;
auto Args = llvm::makeArrayRef(E->getArgs(), E->getNumArgs());
bool HasQualifier = false;
// Extract function decl and 'this' pointer from the callee.
if (CalleeType->isSpecificBuiltinType(BuiltinType::BoundMember)) {
const ValueDecl *Member = nullptr;
const CXXMethodDecl *Member = nullptr;
if (const MemberExpr *ME = dyn_cast<MemberExpr>(Callee)) {
// Explicit bound member calls, such as x.f() or p->g();
if (!EvaluateObjectArgument(Info, ME->getBase(), ThisVal))
return false;
Member = ME->getMemberDecl();
Member = dyn_cast<CXXMethodDecl>(ME->getMemberDecl());
if (!Member)
return Error(Callee);
This = &ThisVal;
HasQualifier = ME->hasQualifier();
} else if (const BinaryOperator *BE = dyn_cast<BinaryOperator>(Callee)) {
// Indirect bound member calls ('.*' or '->*').
Member = HandleMemberPointerAccess(Info, BE, ThisVal, false);
if (!Member) return false;
Member = dyn_cast_or_null<CXXMethodDecl>(
HandleMemberPointerAccess(Info, BE, ThisVal, false));
if (!Member)
return Error(Callee);
This = &ThisVal;
} else
return Error(Callee);
FD = dyn_cast<FunctionDecl>(Member);
if (!FD)
return Error(Callee);
FD = Member;
} else if (CalleeType->isFunctionPointerType()) {
LValue Call;
if (!EvaluatePointer(Callee, Call, Info))
@ -5127,8 +5296,20 @@ public:
} else
return Error(E);
if (This && !checkMemberCallThisPointer(Info, E, *This))
return false;
SmallVector<QualType, 4> CovariantAdjustmentPath;
if (This) {
// Check that the 'this' pointer points to an object of the right type.
if (!checkMemberCallThisPointer(Info, E, *This))
return false;
// Perform virtual dispatch, if necessary.
auto *NamedMember = dyn_cast<CXXMethodDecl>(FD);
if (NamedMember && NamedMember->isVirtual() && !HasQualifier) {
if (!(FD = HandleVirtualDispatch(Info, E, *This, NamedMember,
CovariantAdjustmentPath)))
return true;
}
}
const FunctionDecl *Definition = nullptr;
Stmt *Body = FD->getBody(Definition);
@ -5138,6 +5319,11 @@ public:
Result, ResultSlot))
return false;
if (!CovariantAdjustmentPath.empty() &&
!HandleCovariantReturnAdjustment(Info, E, Result,
CovariantAdjustmentPath))
return false;
return true;
}

View File

@ -1596,6 +1596,9 @@ bool Sema::CheckConstexprFunctionDecl(const FunctionDecl *NewFD) {
// The definition of a constexpr constructor shall satisfy the following
// constraints:
// - the class shall not have any virtual base classes;
//
// FIXME: This only applies to constructors, not arbitrary member
// functions.
const CXXRecordDecl *RD = MD->getParent();
if (RD->getNumVBases()) {
Diag(NewFD->getLocation(), diag::err_constexpr_virtual_base)
@ -1612,21 +1615,25 @@ bool Sema::CheckConstexprFunctionDecl(const FunctionDecl *NewFD) {
// C++11 [dcl.constexpr]p3:
// The definition of a constexpr function shall satisfy the following
// constraints:
// - it shall not be virtual;
// - it shall not be virtual; (removed in C++20)
const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(NewFD);
if (Method && Method->isVirtual()) {
Method = Method->getCanonicalDecl();
Diag(Method->getLocation(), diag::err_constexpr_virtual);
if (getLangOpts().CPlusPlus2a) {
Diag(Method->getLocation(), diag::warn_cxx17_compat_constexpr_virtual);
} else {
Method = Method->getCanonicalDecl();
Diag(Method->getLocation(), diag::err_constexpr_virtual);
// If it's not obvious why this function is virtual, find an overridden
// function which uses the 'virtual' keyword.
const CXXMethodDecl *WrittenVirtual = Method;
while (!WrittenVirtual->isVirtualAsWritten())
WrittenVirtual = *WrittenVirtual->begin_overridden_methods();
if (WrittenVirtual != Method)
Diag(WrittenVirtual->getLocation(),
diag::note_overridden_virtual_function);
return false;
// If it's not obvious why this function is virtual, find an overridden
// function which uses the 'virtual' keyword.
const CXXMethodDecl *WrittenVirtual = Method;
while (!WrittenVirtual->isVirtualAsWritten())
WrittenVirtual = *WrittenVirtual->begin_overridden_methods();
if (WrittenVirtual != Method)
Diag(WrittenVirtual->getLocation(),
diag::note_overridden_virtual_function);
return false;
}
}
// - its return type shall be a literal type;
@ -15197,7 +15204,8 @@ void Sema::MarkVirtualMemberExceptionSpecsNeeded(SourceLocation Loc,
}
void Sema::MarkVirtualMembersReferenced(SourceLocation Loc,
const CXXRecordDecl *RD) {
const CXXRecordDecl *RD,
bool ConstexprOnly) {
// Mark all functions which will appear in RD's vtable as used.
CXXFinalOverriderMap FinalOverriders;
RD->getFinalOverriders(FinalOverriders);
@ -15212,7 +15220,7 @@ void Sema::MarkVirtualMembersReferenced(SourceLocation Loc,
// C++ [basic.def.odr]p2:
// [...] A virtual member function is used if it is not pure. [...]
if (!Overrider->isPure())
if (!Overrider->isPure() && (!ConstexprOnly || Overrider->isConstexpr()))
MarkFunctionReferenced(Loc, Overrider);
}
}

View File

@ -2082,6 +2082,7 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation,
LateInstantiatedAttrVec LateAttrs;
Instantiator.enableLateAttributeInstantiation(&LateAttrs);
bool MightHaveConstexprVirtualFunctions = false;
for (auto *Member : Pattern->decls()) {
// Don't instantiate members not belonging in this semantic context.
// e.g. for:
@ -2128,6 +2129,10 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation,
Instantiation->setInvalidDecl();
break;
}
} else if (CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(NewMember)) {
if (MD->isConstexpr() && !MD->getFriendObjectKind() &&
(MD->isVirtualAsWritten() || Instantiation->getNumBases()))
MightHaveConstexprVirtualFunctions = true;
}
if (NewMember->isInvalidDecl())
@ -2220,9 +2225,14 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation,
Consumer.HandleTagDeclDefinition(Instantiation);
// Always emit the vtable for an explicit instantiation definition
// of a polymorphic class template specialization.
// of a polymorphic class template specialization. Otherwise, eagerly
// instantiate only constexpr virtual functions in preparation for their use
// in constant evaluation.
if (TSK == TSK_ExplicitInstantiationDefinition)
MarkVTableUsed(PointOfInstantiation, Instantiation, true);
else if (MightHaveConstexprVirtualFunctions)
MarkVirtualMembersReferenced(PointOfInstantiation, Instantiation,
/*ConstexprOnly*/ true);
}
return Instantiation->isInvalidDecl();

View File

@ -20,7 +20,10 @@ struct Literal {
};
struct S {
virtual int ImplicitlyVirtual() const = 0; // expected-note {{overridden virtual function}}
virtual int ImplicitlyVirtual() const = 0;
#if __cplusplus <= 201703L
// expected-note@-2 {{overridden virtual function}}
#endif
};
struct SS : S {
int ImplicitlyVirtual() const;
@ -32,12 +35,21 @@ struct T : SS, NonLiteral {
constexpr T();
constexpr int f() const;
// - it shall not be virtual;
virtual constexpr int ExplicitlyVirtual() const { return 0; } // expected-error {{virtual function cannot be constexpr}}
// - it shall not be virtual; [until C++20]
virtual constexpr int ExplicitlyVirtual() const { return 0; }
#if __cplusplus <= 201703L
// expected-error@-2 {{virtual function cannot be constexpr}}
#endif
constexpr int ImplicitlyVirtual() const { return 0; } // expected-error {{virtual function cannot be constexpr}}
constexpr int ImplicitlyVirtual() const { return 0; }
#if __cplusplus <= 201703L
// expected-error@-2 {{virtual function cannot be constexpr}}
#endif
virtual constexpr int OutOfLineVirtual() const; // expected-error {{virtual function cannot be constexpr}}
virtual constexpr int OutOfLineVirtual() const;
#if __cplusplus <= 201703L
// expected-error@-2 {{virtual function cannot be constexpr}}
#endif
// - its return type shall be a literal type;
constexpr NonLiteral NonLiteralReturn() const { return {}; } // expected-error {{constexpr function's return type 'NonLiteral' is not a literal type}}

View File

@ -52,9 +52,19 @@ namespace dr1872 { // dr1872: 9
struct Z : virtual X {};
constexpr int x = A<X>().f();
constexpr int y = A<Y>().f(); // expected-error {{constant expression}} expected-note {{call to virtual function}}
constexpr int y = A<Y>().f();
#if __cplusplus <= 201703L
// expected-error@-2 {{constant expression}} expected-note@-2 {{call to virtual function}}
#else
static_assert(y == 0);
#endif
// Note, this is invalid even though it would not use virtual dispatch.
constexpr int y2 = A<Y>().A<Y>::f(); // expected-error {{constant expression}} expected-note {{call to virtual function}}
constexpr int y2 = A<Y>().A<Y>::f();
#if __cplusplus <= 201703L
// expected-error@-2 {{constant expression}} expected-note@-2 {{call to virtual function}}
#else
static_assert(y == 0);
#endif
constexpr int z = A<Z>().f(); // expected-error {{constant expression}} expected-note {{non-literal type}}
#endif
}

View File

@ -479,12 +479,21 @@ namespace dr647 { // dr647: yes
// This is partially superseded by dr1358.
struct A {
constexpr virtual void f() const;
constexpr virtual void g() const {} // expected-error {{virtual function cannot be constexpr}}
constexpr virtual void g() const {}
#if __cplusplus <= 201703L
// expected-error@-2 {{virtual function cannot be constexpr}}
#endif
};
struct X { virtual void f() const; }; // expected-note {{overridden}}
struct X { virtual void f() const; };
#if __cplusplus <= 201703L
// expected-note@-2 {{overridden}}
#endif
struct B : X {
constexpr void f() const {} // expected-error {{virtual function cannot be constexpr}}
constexpr void f() const {}
#if __cplusplus <= 201703L
// expected-error@-2 {{virtual function cannot be constexpr}}
#endif
};
struct NonLiteral { NonLiteral() {} }; // expected-note {{not an aggregate and has no constexpr constructors}}

View File

@ -211,3 +211,96 @@ constexpr bool for_range_init() {
return k == 6;
}
static_assert(for_range_init());
namespace Virtual {
struct NonZeroOffset { int padding = 123; };
// Ensure that we pick the right final overrider during construction.
struct A {
virtual constexpr char f() const { return 'A'; }
char a = f();
};
struct NoOverrideA : A {};
struct B : NonZeroOffset, NoOverrideA {
virtual constexpr char f() const { return 'B'; }
char b = f();
};
struct NoOverrideB : B {};
struct C : NonZeroOffset, A {
virtual constexpr char f() const { return 'C'; }
A *pba;
char c = ((A*)this)->f();
char ba = pba->f();
constexpr C(A *pba) : pba(pba) {}
};
struct D : NonZeroOffset, NoOverrideB, C { // expected-warning {{inaccessible}}
virtual constexpr char f() const { return 'D'; }
char d = f();
constexpr D() : C((B*)this) {}
};
constexpr D d;
static_assert(((B&)d).a == 'A');
static_assert(((C&)d).a == 'A');
static_assert(d.b == 'B');
static_assert(d.c == 'C');
// During the construction of C, the dynamic type of B's A is B.
static_assert(d.ba == 'B');
static_assert(d.d == 'D');
static_assert(d.f() == 'D');
constexpr const A &a = (B&)d;
constexpr const B &b = d;
static_assert(a.f() == 'D');
static_assert(b.f() == 'D');
// FIXME: It is unclear whether this should be permitted. We assume that
// objects whose values are not known within evaluation are within their
// lifetimes.
D d_not_constexpr;
static_assert(d_not_constexpr.f() == 'D');
// Check that we apply a proper adjustment for a covariant return type.
struct Covariant1 {
D d;
virtual const A *f() const;
};
template<typename T>
struct Covariant2 : Covariant1 {
virtual const T *f() const;
};
template<typename T>
struct Covariant3 : Covariant2<T> {
constexpr virtual const D *f() const { return &this->d; }
};
constexpr Covariant3<B> cb;
constexpr Covariant3<C> cc;
constexpr const Covariant1 *cb1 = &cb;
constexpr const Covariant2<B> *cb2 = &cb;
static_assert(cb1->f()->a == 'A');
static_assert(cb1->f() == (B*)&cb.d);
static_assert(cb1->f()->f() == 'D');
static_assert(cb2->f()->b == 'B');
static_assert(cb2->f() == &cb.d);
static_assert(cb2->f()->f() == 'D');
constexpr const Covariant1 *cc1 = &cc;
constexpr const Covariant2<C> *cc2 = &cc;
static_assert(cc1->f()->a == 'A');
static_assert(cc1->f() == (C*)&cc.d);
static_assert(cc1->f()->f() == 'D');
static_assert(cc2->f()->c == 'C');
static_assert(cc2->f() == &cc.d);
static_assert(cc2->f()->f() == 'D');
static_assert(cb.f()->d == 'D');
static_assert(cc.f()->d == 'D');
struct Abstract {
constexpr virtual void f() = 0; // expected-note {{declared here}}
constexpr Abstract() { do_it(); } // expected-note {{in call to}}
constexpr void do_it() { f(); } // expected-note {{pure virtual function 'Virtual::Abstract::f' called}}
};
struct PureVirtualCall : Abstract { void f(); }; // expected-note {{in call to 'Abstract}}
constexpr PureVirtualCall pure_virtual_call; // expected-error {{constant expression}} expected-note {{in call to 'PureVirtualCall}}
}

View File

@ -63,3 +63,12 @@ void ForRangeInit() {
// expected-warning@-4 {{range-based for loop initialization statements are incompatible with C++ standards before C++2a}}
#endif
}
struct ConstexprVirtual {
virtual constexpr void f() {}
#if __cplusplus <= 201703L
// expected-error@-2 {{virtual function cannot be constexpr}}
#else
// expected-warning@-4 {{virtual constexpr functions are incompatible with C++ standards before C++2a}}
#endif
};

View File

@ -965,7 +965,7 @@ as the draft C++2a standard evolves.
<tr>
<td rowspan=4>Relaxations of <tt>constexpr</tt> restrictions</td>
<td><a href="http://wg21.link/p1064r0">P1064R0</a></td>
<td class="none" align="center">No</td>
<td class="svn" align="center">SVN</td>
</tr>
<tr> <!-- from San Diego -->
<td><a href="http://wg21.link/p1002r1">P1002R1</a></td>