On 7/24/26 1:02 PM, Jakub Jelinek wrote:
On Fri, Jul 24, 2026 at 05:36:01PM +0200, Jakub Jelinek wrote:
Or do you mean I should always unconditionally try
cxx_fold_indirect_ref, if that succeeds, nothing to report, we're done,
if it fails, and tree_strip_nop_conversions (op) is an ADDR_EXPR,
use a loop to skip over all DECL_FIELD_IS_BASE (because those shouldn't
be then matching) and just use it to find out what the dynamic
type is?
I've tried that, but that regresses a few tests:
--- gcc/cp/constexpr.cc.jj 2026-07-24 18:38:54.332025326 +0200
+++ gcc/cp/constexpr.cc 2026-07-24 18:46:44.785857777 +0200
@@ -10259,6 +10259,44 @@ cxx_eval_constant_expression (const cons
}
}
+ /* [expr.static.cast]/10: A prvalue of type "pointer to cv1 B", where
+ B is a class type, can be converted to a prvalue of type
+ "pointer to cv2 D", where D is a complete class derived from B, ...
+ If the prvalue of type "pointer to cv1 B" points to a B that is
+ actually a base class subobject of an object of type D, the
+ resulting pointer points to the enclosing object of type D.
+ Otherwise, the behavior is undefined.
+ Similarly [expr.static.cast]/2 for references. */
+ if (INDIRECT_TYPE_P (type)
+ && INDIRECT_TYPE_P (TREE_TYPE (op))
+ && COMPLETE_TYPE_P (TREE_TYPE (type))
+ && !integer_zerop (op)
+ && is_properly_derived_from (TREE_TYPE (type),
+ TREE_TYPE (TREE_TYPE (op)))
+ && cxx_fold_indirect_ref (ctx, loc, TREE_TYPE (type), op,
+ NULL, jump_target) == NULL_TREE)
+ {
+ tree sop;
+ if (!ctx->quiet
+ && (sop = tree_strip_nop_conversions (op))
+ && TREE_CODE (sop) == ADDR_EXPR)
+ {
+ sop = TREE_OPERAND (sop, 0);
+ while (TREE_CODE (sop) == COMPONENT_REF
+ && DECL_FIELD_IS_BASE (TREE_OPERAND (sop, 1)))
+ sop = TREE_OPERAND (sop, 0);
+ error_at (loc, "cannot cast object of dynamic type "
+ "%qT to type %qT",
+ strip_array_types (TREE_TYPE (sop)),
+ TREE_TYPE (type));
+ }
+ else if (!ctx->quiet)
+ error_at (loc, "cannot cast object to type %qT",
+ TREE_TYPE (type));
+ *non_constant_p = true;
+ return t;
+ }
+
if (TREE_CODE (op) == PTRMEM_CST && !TYPE_PTRMEM_P (type))
{
op = cplus_expand_constant (op);
Just on
GXX_TESTSUITE_STDS=14,29 make check-g++ RUNTESTFLAGS="dg.exp='constexpr-base*
constexpr-static-cast*'"
FAIL: g++.dg/cpp0x/constexpr-static-cast2.C -std=c++14 (test for excess errors)
FAIL: g++.dg/cpp0x/constexpr-static-cast2.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp1y/constexpr-base2a.C -std=c++14 (test for excess errors)
FAIL: g++.dg/cpp1y/constexpr-base2a.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp1y/constexpr-static-cast2.C -std=c++14 (test for excess errors)
FAIL: g++.dg/cpp1y/constexpr-static-cast2.C -std=c++29 (test for excess errors)
Excess errors:
/usr/src/gcc/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast2.C:29:47: error:
cannot cast object of dynamic type 'const F' to type 'const F'
/usr/src/gcc/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast2.C:45:49: error:
cannot cast object of dynamic type 'const F' to type 'const F'
/usr/src/gcc/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast2.C:52:49: error:
cannot cast object of dynamic type 'F' to type 'const F'
Excess errors:
/usr/src/gcc/gcc/testsuite/g++.dg/cpp1y/constexpr-base2a.C:17:19: error:
non-constant condition for static assertion
/usr/src/gcc/gcc/testsuite/g++.dg/cpp1y/constexpr-base2a.C:13:6: error: cannot
cast object of dynamic type 'B' to type 'B'
Excess errors:
/usr/src/gcc/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast2.C:87:23: error:
non-constant condition for static assertion
/usr/src/gcc/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast2.C:74:9: error:
cannot cast object of dynamic type 'G' to type 'G'
So, clearly cxx_fold_indirect_ref fails in some valid cases.
Hmm, yes, because e.g. in constexpr-base2a.C the A subobject is at a
non-zero offset so there isn't a B at that address.
This seems to indicate that we're representing the conversion badly.
This is because the cp_fold_convert in build_static_cast_1 turns
NOP_EXPR (B&, NOP_EXPR (B*, POINTER_PLUS_EXPR (NOP_EXPR (A*, a), -4)))
into
POINTER_PLUS_EXPR (NOP_EXPR (B&, a), -4)
so the NOP_EXPR is now an invalid downcast that the patch diagnoses.
This happens in fold_unary_loc:
/* Convert (T1)(X p+ Y) into ((T1)X p+ Y), for pointer type, when the new
cast (T1)X will fold away. We assume that this happens when X itself
is a cast. */
It would be better for this case to leave (T1) outside the p+ while
removing casts from X.
Or perhaps reverse the conversion, i.e. ((T1)X p+ Y) to (T1)(X p+ Y) so
that all the casts are outside the p+ and collapse naturally. So here
the POINTER_PLUS_EXPR would have A& type and then be converted to B&.
Or perhaps build_static_cast_1 should do its own conversion collapsing
(or none) rather than expecting fold to DRRT.
Thoughts?
Jason