[clang] [clang][bytecode] Actually implement `dynamic_cast` (PR #203489)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 12 02:50:52 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Timm Baeder (tbaederr)
<details>
<summary>Changes</summary>
Do a full type search if necessary.
---
Patch is 20.98 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/203489.diff
6 Files Affected:
- (modified) clang/lib/AST/ByteCode/Compiler.cpp (+16-5)
- (modified) clang/lib/AST/ByteCode/Interp.cpp (+205-12)
- (modified) clang/lib/AST/ByteCode/Interp.h (+2-1)
- (modified) clang/lib/AST/ByteCode/InterpState.h (+1)
- (modified) clang/lib/AST/ByteCode/Opcodes.td (+3-1)
- (added) clang/test/AST/ByteCode/dynamic-cast.cpp (+283)
``````````diff
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 8ea42fea03bee..638e6ecafb295 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -823,9 +823,7 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *E) {
return discard(SubExpr);
case CK_Dynamic:
- // This initially goes through VisitCXXDynamicCastExpr, where we emit
- // a diagnostic if appropriate.
- return this->delegate(SubExpr);
+ llvm_unreachable("CXXDynamicCastExpr has its own function");
case CK_LValueBitCast:
if (!this->emitInvalidCast(CastKind::ReinterpretLike, /*Fatal=*/false, E))
@@ -3543,16 +3541,29 @@ bool Compiler<Emitter>::VisitCXXReinterpretCastExpr(
template <class Emitter>
bool Compiler<Emitter>::VisitCXXDynamicCastExpr(const CXXDynamicCastExpr *E) {
-
if (!Ctx.getLangOpts().CPlusPlus20) {
if (!this->emitInvalidCast(CastKind::Dynamic, /*Fatal=*/false, E))
return false;
+ }
+
+ if (E->getCastKind() != CK_Dynamic)
return this->VisitCastExpr(E);
+
+ QualType DestType = E->getType();
+ // "target type must be a reference or pointer type to a defined class"
+ if (DestType->isRecordType()) {
+ assert(E->isGLValue());
+ } else {
+ assert(DestType->isPointerOrReferenceType());
+ assert(DestType->isVoidPointerType() ||
+ DestType->getPointeeType()->isRecordType());
+ DestType = DestType->getPointeeType();
}
if (!this->visit(E->getSubExpr()))
return false;
- if (!this->emitCheckDynamicCast(E))
+ if (!this->emitDynamicCast(DestType.getTypePtr(),
+ /*IsReferenceCast=*/E->isGLValue(), E))
return false;
if (DiscardResult)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index f21dbb3d5246c..dd243675d9cff 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1844,8 +1844,10 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
if (Func->isDestructor() && !CheckDestructor(S, OpPC, ThisPtr))
return false;
- if (Func->isConstructor() || Func->isDestructor())
+ if (Func->isConstructor() || Func->isDestructor()) {
+ S.InitializingPtrs.push_back(ThisPtr);
S.InitializingBlocks.push_back(ThisPtr.block());
+ }
}
if (!Func->isFullyCompiled())
@@ -1874,8 +1876,10 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
// have a caller set.
bool Success = Interpret(S);
// Remove initializing block again.
- if (Func->isConstructor() || Func->isDestructor())
+ if (Func->isConstructor() || Func->isDestructor()) {
S.InitializingBlocks.pop_back();
+ S.InitializingPtrs.pop_back();
+ }
if (!Success) {
InterpFrame::free(NewFrame);
@@ -1918,19 +1922,208 @@ static bool getDynamicDecl(InterpState &S, CodePtr OpPC, Pointer TypePtr,
return DynamicDecl != nullptr;
}
-bool CheckDynamicCast(InterpState &S, CodePtr OpPC) {
- const auto &Ptr = S.Stk.peek<Pointer>();
+struct DynamicCastResult {
+ UnsignedOrNone Offset = std::nullopt;
+ bool Ambiguous = false;
+
+ bool valid() const { return !Ambiguous && Offset; }
+
+ void setOffset(unsigned O) {
+ if (!Offset)
+ Offset = O;
+ else {
+ Ambiguous = true;
+ }
+ }
+
+ void merge(DynamicCastResult C) {
+ Ambiguous |= C.Ambiguous;
+ if (C.Offset) {
+ if (!Offset)
+ Offset = C.Offset;
+ else
+ Ambiguous = true;
+ }
+ }
+};
+
+
+// Walk UP the type hierarchy, starting at the decl of R to find Needle.
+static DynamicCastResult findRecordBase(const ASTContext &Ctx, const Record *R,
+ QualType Needle) {
+ DynamicCastResult Res;
+
+ if (Ctx.hasSimilarType(Needle, Ctx.getCanonicalTagType(R->getDecl())))
+ Res.setOffset(0);
- if (!Ptr.isConstexprUnknown())
+ for (const Record::Base &B : R->bases()) {
+ auto N = findRecordBase(Ctx, B.R, Needle);
+ if (N.Offset)
+ N.Offset = *N.Offset + B.Offset;
+ Res.merge(N);
+ }
+
+ return Res;
+}
+
+bool DynamicCast(InterpState &S, CodePtr OpPC, const Type *DestTypePtr,
+ bool IsReferenceCast) {
+ const auto &Ptr = S.Stk.pop<Pointer>();
+ QualType TargetType = QualType(DestTypePtr, 0);
+
+ // TODO: Use PtrView once it's merged.
+
+ if (Ptr.isConstexprUnknown()) {
+ QualType T = Ptr.getType();
+ const Expr *E = S.Current->getExpr(OpPC);
+ APValue V = Ptr.toAPValue(S.getASTContext());
+ QualType TT = S.getASTContext().getLValueReferenceType(T);
+ S.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type)
+ << AK_DynamicCast << V.getAsString(S.getASTContext(), TT);
+ return false;
+ }
+
+ // TODO: Other checks?
+ if (Ptr.isRoot() || !Ptr.isBlockPointer())
+ return false;
+
+ // Our given pointer, limited by the base that's currently being initialized,
+ // if any.
+ Pointer LimitedPtr;
+ if (S.InitializingPtrs.empty()) {
+ LimitedPtr = Ptr.stripBaseCasts();
+ } else {
+ // FIXME: Is this always the correct block?
+ LimitedPtr = S.InitializingPtrs.back();
+ assert(LimitedPtr.block() == Ptr.block());
+ }
+ assert(LimitedPtr.getRecord());
+
+ // C++ [expr.dynamic.cast]p7:
+ // If T is "pointer to cv void", then the result is a pointer to the most
+ // derived object
+ if (TargetType->isVoidType()) {
+ S.Stk.push<Pointer>(LimitedPtr);
return true;
+ }
- QualType T = Ptr.getType();
- const Expr *E = S.Current->getExpr(OpPC);
- APValue V = Ptr.toAPValue(S.getASTContext());
- QualType TT = S.getASTContext().getLValueReferenceType(T);
- S.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type)
- << AK_DynamicCast << V.getAsString(S.getASTContext(), TT);
- return false;
+ assert(!TargetType.isNull());
+ assert(!TargetType->isVoidType());
+ assert(TargetType->isRecordType());
+
+ // Helper lambdas.
+ auto typesMatch = [&](QualType A, QualType B) -> bool {
+ return S.getASTContext().hasSimilarType(A, B);
+ };
+ auto getRecord = [](const Pointer &P) -> const CXXRecordDecl * {
+ assert(P.getRecord());
+ return cast<CXXRecordDecl>(P.getRecord()->getDecl());
+ };
+
+ auto baseIsPrivate = [&](const Pointer &P) -> bool {
+ if (P.isRoot() || !P.isBaseClass())
+ return false;
+
+ CXXBasePaths Paths;
+ getRecord(P.getBase())->isDerivedFrom(getRecord(P), Paths);
+ assert(std::distance(Paths.begin(), Paths.end()) == 1);
+
+ return Paths.front().Access == AS_private;
+ };
+
+ enum {
+ DiagPrivateBase = 0,
+ DiagNoBase = 1,
+ DiagAmbiguous = 2,
+ DiagPrivateSibling = 3
+ };
+
+ auto diag = [&](int DiagKind, QualType ResultType) -> bool {
+ // Pointer casts return nullptr on failure.
+ if (!IsReferenceCast) {
+ S.Stk.push<Pointer>(0, DestTypePtr);
+ return true;
+ }
+ QualType DynamicType = LimitedPtr.getType()->getCanonicalTypeUnqualified();
+ S.FFDiag(S.Current->getSource(OpPC),
+ diag::note_constexpr_dynamic_cast_to_reference_failed)
+ << DiagKind << ResultType << DynamicType << TargetType;
+ return false;
+ };
+
+ // Check if Ptr's dynamic type is derived from our target type at all.
+ // If it isn't, diagnose this as "operand does not have base class of type
+ // [...]".
+ {
+ CXXBasePaths Paths;
+ getRecord(LimitedPtr)
+ ->isDerivedFrom(TargetType->getAsCXXRecordDecl(), Paths);
+ if (std::distance(Paths.begin(), Paths.end()) == 0 &&
+ !typesMatch(LimitedPtr.getType(), TargetType)) {
+ return diag(DiagNoBase, TargetType);
+ }
+ }
+
+ // Current base is already private.
+ if (baseIsPrivate(Ptr))
+ return diag(DiagPrivateBase, Ptr.getType());
+
+ std::optional<Pointer> Result;
+ // First, check simple downcasts without ambiguities.
+ for (Pointer Iter = Ptr;;) {
+ if (typesMatch(TargetType, Iter.getType())) {
+ Result = Iter;
+ break;
+ }
+ // Moving DOWN the type hierarchy.
+ Iter = Iter.getBase();
+ if (Iter.isRoot() || !Iter.isBaseClass())
+ break;
+ }
+
+ // Simply walking down the type hierarchy has produced a valid result, use
+ // that.
+ if (Result) {
+ if (baseIsPrivate(*Result))
+ return diag(DiagPrivateBase, Result->getType());
+ S.Stk.push<Pointer>(*Result);
+ return true;
+ }
+
+ // Otherwise, we need to do a deep hierarchy check.
+ bool Ambiguous = false;
+ for (Pointer Iter = LimitedPtr;;) {
+ // If we can move up the hierarchy from this level and reach the target type
+ // unambiguously, we're fine.
+ auto R = findRecordBase(S.getASTContext(), Iter.getRecord(), TargetType);
+
+ if (R.valid()) {
+ Result = Iter.atField(*R.Offset);
+ break;
+ } else if (R.Ambiguous) {
+ Ambiguous = true;
+ break;
+ }
+
+ // This moves us DOWN the type hierarchy.
+ Iter = Iter.getBase();
+ if (Iter.isRoot() || !Iter.isBaseClass())
+ break;
+ }
+
+ if (Ambiguous)
+ return diag(DiagAmbiguous, TargetType);
+
+ if (Result) {
+ // Might still be invalid due to resulting in a private base though.
+ if (baseIsPrivate(*Result))
+ return diag(DiagPrivateSibling, TargetType);
+ S.Stk.push<Pointer>(*Result);
+ return true;
+ }
+
+ // We couldn't find the requested base.
+ return diag(DiagNoBase, TargetType);
}
bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func,
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 3f082f15ffacc..d292ccbe64bf4 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -143,7 +143,8 @@ bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC,
bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I);
bool isConstexprUnknown(const Pointer &P);
bool isConstexprUnknown(const Block *B);
-bool CheckDynamicCast(InterpState &S, CodePtr OpPC);
+bool DynamicCast(InterpState &S, CodePtr OpPC, const Type *DestType,
+ bool IsReferenceCast);
enum class ShiftDir { Left, Right };
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index b6ef985095907..4614a0b29a185 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -175,6 +175,7 @@ class InterpState final : public State, public SourceMapper {
/// List of blocks we're currently running either constructors or destructors
/// for.
llvm::SmallVector<const Block *> InitializingBlocks;
+ llvm::SmallVector<Pointer> InitializingPtrs;
};
class InterpStateCCOverride final {
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 09c616aa2ff1d..d944c964cc919 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -849,7 +849,9 @@ def SideEffect : Opcode {}
def InvalidCast : Opcode {
let Args = [ArgCastKind, ArgBool];
}
-def CheckDynamicCast : Opcode {}
+def DynamicCast : Opcode {
+ let Args = [ArgTypePtr, ArgBool];
+}
def InvalidStore : Opcode { let Args = [ArgTypePtr]; }
def CheckPseudoDtor : Opcode {}
diff --git a/clang/test/AST/ByteCode/dynamic-cast.cpp b/clang/test/AST/ByteCode/dynamic-cast.cpp
new file mode 100644
index 0000000000000..7e5aa61703c69
--- /dev/null
+++ b/clang/test/AST/ByteCode/dynamic-cast.cpp
@@ -0,0 +1,283 @@
+// RUN: %clang_cc1 -verify=ref,both -std=c++26 %s
+// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both -std=c++26 %s
+
+namespace Simple {
+
+ struct S {};
+ constexpr S ss;
+ constexpr int foo = (dynamic_cast<const S &>(ss), 0);
+
+ struct S1 { virtual void a(); };
+ struct S2 : S1 {};
+ constexpr S2 s{};
+ static_assert(dynamic_cast<const S2*>(static_cast<const S1*>(&s)) == &s);
+}
+
+namespace Failing {
+}
+
+namespace NotSoSimple {
+ struct A2 { virtual void a2(); };
+ struct D {
+ virtual void d();
+ };
+ struct F : A2, D {
+ void *f = dynamic_cast<void*>( static_cast<D*>(this) );
+ };
+ constexpr F g;
+ static_assert(g.f == (void*)(F*)&g);
+
+ constexpr D* d = (D*)&g;
+ constexpr void* f = dynamic_cast<void*>(d);
+ static_assert(f == &g);
+}
+
+
+namespace Again {
+ struct A3 {};
+ struct A4 {};
+
+ struct A2 : A3, A4 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 { A4 *c = dynamic_cast<A4*>(static_cast<C2*>(this)); };
+
+ struct D { virtual void d(); };
+
+ struct F : C, D{};
+ struct G : F {};
+ constexpr G g;
+ static_assert(g.c == (C*)&g);
+}
+
+namespace FailedReference {
+ struct A {};
+ struct K : A {};
+ struct P : A {};
+
+ struct L{virtual void p();};
+
+ struct S : P, K, L {
+ virtual void p();
+ };
+ constexpr S s{};
+ static_assert(&dynamic_cast<const A&>((L&)s) == nullptr); // both-error {{not an integral constant expression}} \
+ // both-note {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'FailedReference::S' of operand}}
+}
+
+namespace FailedReference2 {
+ struct A2 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct B : A {};
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 { A *c = dynamic_cast<A*>(static_cast<C2*>(this)); };
+ struct D { virtual void d(); };
+ struct E { virtual void e(); };
+ struct F : B, C, D, private E { void *f = dynamic_cast<void*>(static_cast<D*>(this)); };
+ struct G : F {};
+
+ constexpr G g;
+ static_assert(&dynamic_cast<A&>((D&)g) == nullptr); // both-error {{not an integral constant expression}} \
+ // both-note {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'FailedReference2::G' of operand}}
+}
+
+namespace FailedPtr {
+ struct A {};
+
+ struct B : A {};
+
+ struct C2 { virtual void c2(); };
+
+ struct C : A, C2 {};
+ struct F : B, C {};
+ constexpr F g;
+ static_assert(dynamic_cast<const A*>(static_cast<const C2*>(&g)) == nullptr);
+ static_assert(dynamic_cast<const B*>(static_cast<const C2*>(&g)) != nullptr);
+}
+
+namespace Initializing {
+ struct A2 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct B : A {};
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 { A *c = dynamic_cast<A*>(static_cast<C2*>(this)); };
+
+ struct D { virtual void d(); };
+ struct E { virtual void e(); };
+ struct F : B, C, D {};
+ struct Padding { virtual void padding(); };
+ struct G : Padding, F {};
+
+ constexpr G g;
+
+ // During construction of C, A is unambiguous subobject of dynamic type C.
+ static_assert(g.c == (C*)&g);
+}
+
+namespace SimpleDowncast {
+ struct A2 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct B : A {};
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 {};
+ struct D { virtual void d(); };
+ struct E { virtual void e(); };
+ struct F : B, C, D {};
+ struct Padding { virtual void padding(); };
+ struct G : Padding, F {};
+
+ constexpr G g;
+ // Can navigate from A2 to its A...
+ static_assert(&dynamic_cast<A&>((A2&)(B&)g) == &(A&)(B&)g);
+}
+
+namespace ActuallyADerived2BaseCast {
+ struct A2 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct B : A {};
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 {};
+ struct D { virtual void d(); };
+ struct E { virtual void e(); };
+ struct F : B, C, D {};
+ struct Padding { virtual void padding(); };
+ struct G : Padding, F {};
+ constexpr G g;
+ // ... and from B to its A ...
+ static_assert(&dynamic_cast<A2&>((B&)g) == &(A2&)(B&)g);
+}
+
+namespace ProperLimitedPtrInVoidCast {
+ struct A2 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct B : A {};
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 {};
+ struct D { virtual void d(); };
+ struct E { virtual void e(); };
+ struct F : B, C, D, private E { void *f = dynamic_cast<void*>(static_cast<D*>(this)); };
+ struct Padding { virtual void padding(); };
+ struct G : Padding, F {};
+
+ constexpr G g;
+ static_assert(g.f == (void*)(F*)&g);
+}
+
+namespace Unrelated {
+ struct A2 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct B : A {};
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 {};
+ struct D { virtual void d(); };
+ struct E { virtual void e(); };
+ struct F : B, C, D, private E {};
+ struct Padding { virtual void padding(); };
+ struct G : Padding, F {};
+
+ constexpr G g;
+ struct Unrelated { virtual void unrelated(); };
+ constexpr int b_unrelated = (dynamic_cast<Unrelated&>((B&)g), 0); // both-error {{must be initialized by a constant expression}} \
+ // both-note {{reference dynamic_cast failed: dynamic type 'Unrelated::G' of operand does not have a base class of type 'Unrelated'}}
+ constexpr int e_unrelated = (dynamic_cast<Unrelated&>((E&)g), 0); // both-error {{must be initialized by a constant expression}} \
+ // both-note {{reference dynamic_cast failed: dynamic type 'Unrelated::G' of operand does not have a base class of type 'Unrelated'}}
+ static_assert(dynamic_cast<Unrelated*>((B*)&g) == nullptr);
+ static_assert(dynamic_cast<Unrelated*>((E*)&g) == nullptr);
+}
+
+namespace PrivateSibling {
+ struct A2 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct B : A {};
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 {};
+ struct D { virtual void d(); };
+ struct E { virtual void e(); };
+ struct F : B, C, D, private E {};
+ struct Padding { virtual void padding(); };
+ struct G : Padding, F {};
+
+ constexpr G g;
+ // Cannot cast from B to private sibling E.
+ constexpr int b_e = (dynamic_cast<E&>((B&)g), 0); // both-error {{must be initialized by a constant expression}} \
+ // both-note {{reference dynamic_cast failed: 'E' is a non-public base class of dynamic type 'PrivateSibling::G' of operand}}
+ static_assert(dynamic_cast<E*>((B*)&g) == nullptr);
+}
+
+namespace Field {
+ struct X {
+ mutable int n = 0;
+ virtual constexpr ~X() {}
+ };
+ struct Y : X {
+ };
+ struct Z {
+ mutable Y y;
+ };
+ constexpr Z z;
+ constexpr const X *pz = &z.y;
+ constexpr const Y *qz = dynamic_cast<const Y*>(pz);
+ static_assert(qz != nullptr);
+}
+
+/// The entire DynamicCast test from constant-expression-cxx2a.cpp but the g variable is a field.
+namespace Field2 {
+ struct A2 { virtual void a2(); };
+ struct A : A2 { virtual void a(); };
+ struct B : A {};
+ struct C2 { virtual void c2(); };
+ struct C : A, C2 { A *c = dynamic_cast<A*>(static_cast<C2*>(this)); };
+ struct D { virtual void d(); };
+ struct E { virtual void e(); };
+ struct F : B, C, D, private E { void *f = dynamic_cast<void*>(static_cast<D*>(this)); };
+ struct Padding { virtual void padding(); };
+ struct G : Padding, F {};
+
+
+ struct SomeStruct {
+ int a;
+ int b;
+ G g;
+ };
+
+ constexpr SomeStruct ss{};
+
+ // During construction of C, A is unambiguous subobject of dynamic type C.
+ static_assert(ss.g.c == (C*)&ss.g);
+ // ... but in the complete object, the same is not true, so the runtime fails.
+ static_assert(dynamic_cast<const A*>(static_cast<const C2*>(&ss.g)) == nullptr);
+
+ // dynamic_cast<void*> produces a pointer to the object of the dynamic type.
+ static_assert(ss.g.f == (void*)(F*)&ss.g);
+ static_assert(dynamic_cast<const void*>(static_cast<const D*>(&ss.g)) == &ss.g);
+
+ // both-note at +1 {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'Field2::G' of operand}}
+ constexpr int d_a = (dynamic_cast<const A&>(static_cast<const D&>(ss.g)), 0); // both-error {{}}
+
+ // Can navigate from A2 to its A...
+ static_assert(&dynamic_cast<A&>((A2&)(B&)ss.g) == &(A&)(B&)ss.g);
+ // ... and from B to its A ...
+ static_assert(&dynamic_cast<A&>((B&)ss.g) == &(A&)(B&)ss.g);
+ // ... but not from D.
+ // both-note at +1 {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'Field2::G' of operand}}
+ static_assert(&dynamic_cast<A&>((D&)ss.g) == &(A&)(B&)ss.g); // both-error {{}}
+
+ // Can cast from A2 to sibling class D.
+ static_assert(&dynamic_cast<D&>((A2&)(B&)ss.g) == &(D&)ss.g);
+
+ // Cannot cast from private base E...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/203489
More information about the cfe-commits
mailing list