->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) {
"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">;
"%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 "
/// 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
}
CXXMethodDecl *
-CXXMethodDecl::getCorrespondingMethodInClass(const CXXRecordDecl *RD,
- bool MayBeBase) {
+CXXMethodDecl::getCorrespondingMethodDeclaredInClass(const CXXRecordDecl *RD,
+ bool MayBeBase) {
if (this->getParent()->getCanonicalDecl() == RD->getCanonicalDecl())
return this;
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)
#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"
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,
}
// 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) {
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) {
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;
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 &&
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))
} 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);
Result, ResultSlot))
return false;
+ if (!CovariantAdjustmentPath.empty() &&
+ !HandleCovariantReturnAdjustment(Info, E, Result,
+ CovariantAdjustmentPath))
+ return false;
+
return true;
}
// 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)
// 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 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 (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;
+ }
}
// - its return type shall be a literal type;
}
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);
// 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);
}
}
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:
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())
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();
};
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;
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}}
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
}
// 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}}
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}}
+}
// 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
+};
<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>