On 11/21/25 3:17 AM, Nathaniel Shead wrote:
On Thu, Nov 20, 2025 at 10:28:13PM +0530, Jason Merrill wrote:
On 11/20/25 4:02 PM, Nathaniel Shead wrote:
On Wed, Nov 19, 2025 at 12:08:58PM +0530, Jason Merrill wrote:
On 10/26/25 7:33 AM, Nathaniel Shead wrote:
OK for trunk?

-- >8 --

This reorders some checks in layout_compatible_type_p to promote more
useful diagnostics as well and try to avoid duplicate code.

gcc/cp/ChangeLog:

        * constraint.cc (diagnose_trait_expr)
        <case CPTK_IS_LAYOUT_COMPATIBLE>: Explain why not.
        * cp-tree.h (layout_compatible_type_p): Add explain parameter.
        * typeck.cc (layout_compatible_type_p): Add explanations when
        returning false.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp2a/is-layout-compatible4.C: New test.

Signed-off-by: Nathaniel Shead <[email protected]>
---
    gcc/cp/constraint.cc                          |   3 +-
    gcc/cp/cp-tree.h                              |   2 +-
    gcc/cp/typeck.cc                              | 146 +++++++++++++++---
    .../g++.dg/cpp2a/is-layout-compatible4.C      |  86 +++++++++++
    4 files changed, 211 insertions(+), 26 deletions(-)
    create mode 100644 gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 1ab5a2902d3..f55cae37007 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3186,7 +3186,8 @@ diagnose_trait_expr (location_t loc, tree expr, tree args)
          }
          break;
        case CPTK_IS_LAYOUT_COMPATIBLE:
-      inform (loc, "%qT is not layout compatible with %qT", t1, t2);
+      inform (loc, "%qT is not layout compatible with %qT, because", t1, t2);
+      layout_compatible_type_p (t1, t2, /*explain=*/true);
          break;
        case CPTK_IS_LITERAL_TYPE:
          inform (decl_loc, "%qT is not a literal type", t1);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 844dc3a577e..339ea062cd0 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8550,7 +8550,7 @@ extern bool same_type_ignoring_top_level_qualifiers_p 
(tree, tree);
    extern bool similar_type_p                  (tree, tree);
    extern bool cp_comp_parm_types                      (tree, tree);
    extern bool next_common_initial_sequence    (tree &, tree &);
-extern bool layout_compatible_type_p           (tree, tree);
+extern bool layout_compatible_type_p           (tree, tree, bool = false);
    extern bool compparms                               (const_tree, 
const_tree);
    extern int comp_cv_qualification            (const_tree, const_tree);
    extern int comp_cv_qualification            (int, int);
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index dbadeb77085..97e96d0b045 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -1870,44 +1870,106 @@ next_common_initial_sequence (tree &memb1, tree &memb2)
    /* Return true if TYPE1 and TYPE2 are layout-compatible types.  */
    bool
-layout_compatible_type_p (tree type1, tree type2)
+layout_compatible_type_p (tree type1, tree type2, bool explain/*=false*/)
    {
      if (type1 == error_mark_node || type2 == error_mark_node)
        return false;
      if (type1 == type2)
        return true;
-  if (TREE_CODE (type1) != TREE_CODE (type2))
-    return false;
      type1 = cp_build_qualified_type (type1, TYPE_UNQUALIFIED);
      type2 = cp_build_qualified_type (type2, TYPE_UNQUALIFIED);
+  if (same_type_p (type1, type2))
+    return true;
+
+  if (TREE_CODE (type1) != TREE_CODE (type2)
+      || (TREE_CODE (type1) != ENUMERAL_TYPE
+         && !CLASS_TYPE_P (type1))
+      || (TREE_CODE (type2) != ENUMERAL_TYPE
+         && !CLASS_TYPE_P (type2)))
+    {
+      if (explain)
+       inform (input_location, "%q#T and %q#T are not both the same type, "
+               "layout-compatible enumerations, or "
+               "layout-compatible standard-layout class types",
+               type1, type2);
+      return false;
+    }
      if (TREE_CODE (type1) == ENUMERAL_TYPE)
-    return (tree_int_cst_equal (TYPE_SIZE (type1), TYPE_SIZE (type2))
-           && same_type_p (finish_underlying_type (type1),
-                           finish_underlying_type (type2)));
+    {
+      tree underlying1 = finish_underlying_type (type1);
+      tree underlying2 = finish_underlying_type (type2);
+      if (!same_type_p (underlying1, underlying2))
+       {
+         if (explain)
+           {
+             inform (location_of (type1),
+                     "the underlying type of %qT is %qT, but",
+                     type1, underlying1);
+             inform (location_of (type2),
+                     "the underlying type of %qT is %qT",
+                     type2, underlying2);
+           }
+         return false;
+       }
+    }
+  else
+    gcc_checking_assert (CLASS_TYPE_P (type1) && CLASS_TYPE_P (type2));
-  if (CLASS_TYPE_P (type1)
-      && std_layout_type_p (type1)
-      && std_layout_type_p (type2)
-      && tree_int_cst_equal (TYPE_SIZE (type1), TYPE_SIZE (type2)))
+  if (!std_layout_type_p (type1))
+    {
+      if (explain)
+       inform (location_of (type1),
+               "%qT is not a standard-layout type", type1);
+      return false;
+    }
+  if (!std_layout_type_p (type2))
+    {
+      if (explain)
+       inform (location_of (type2),
+               "%qT is not a standard-layout type", type2);
+      return false;
+    }
+
+  if (TREE_CODE (type1) == RECORD_TYPE)
        {
          tree field1 = TYPE_FIELDS (type1);
          tree field2 = TYPE_FIELDS (type2);
-      if (TREE_CODE (type1) == RECORD_TYPE)
+      while (1)
        {
-         while (1)
+         if (!next_common_initial_sequence (field1, field2))
            {
-             if (!next_common_initial_sequence (field1, field2))
-               return false;
-             if (field1 == NULL_TREE)
-               return true;
-             field1 = DECL_CHAIN (field1);
-             field2 = DECL_CHAIN (field2);
+             if (explain)
+               {
+                 if (field1 && field2)
+                   {
+                     inform (DECL_SOURCE_LOCATION (field1),
+                             "%qD and %qD do not correspond",
+                             field1, field2);
+                     inform (DECL_SOURCE_LOCATION (field2),
+                             "%qD declared here", field2);
+                   }
+                 else if (field1)
+                   inform (DECL_SOURCE_LOCATION (field1),
+                           "%qT has no member corresponding to %qD",
+                           type2, field1);
+                 else if (field2)
+                   inform (DECL_SOURCE_LOCATION (field2),
+                           "%qT has no member corresponding to %qD",
+                           type1, field2);
+               }
+             return false;
            }
+         if (field1 == NULL_TREE)
+           break;
+         field1 = DECL_CHAIN (field1);
+         field2 = DECL_CHAIN (field2);
        }
-      /* Otherwise both types must be union types.
-        The standard says:
+    }
+  else if (TREE_CODE (type1) == UNION_TYPE)
+    {
+      /* The standard says:
         "Two standard-layout unions are layout-compatible if they have
         the same number of non-static data members and corresponding
         non-static data members (in any order) have layout-compatible
@@ -1915,6 +1977,8 @@ layout_compatible_type_p (tree type1, tree type2)
         but the code anticipates that bitfield vs. non-bitfield,
         different bitfield widths or presence/absence of
         [[no_unique_address]] should be checked as well.  */
+      tree field1 = TYPE_FIELDS (type1);
+      tree field2 = TYPE_FIELDS (type2);
          auto_vec<tree, 16> vec;
          unsigned int count = 0;
          for (; field1; field1 = DECL_CHAIN (field1))
@@ -1923,11 +1987,26 @@ layout_compatible_type_p (tree type1, tree type2)
          for (; field2; field2 = DECL_CHAIN (field2))
        if (TREE_CODE (field2) == FIELD_DECL)
          vec.safe_push (field2);
+
          /* Discussions on core lean towards treating multiple union fields
         of the same type as the same field, so this might need changing
         in the future.  */
          if (count != vec.length ())
-       return false;
+       {
+         if (explain)
+           {
+             inform_n (location_of (type1), count,
+                       "%qT has %u field, but",
+                       "%qT has %u fields, but",
+                       type1, count);
+             inform_n (location_of (type2), vec.length (),
+                       "%qT has %u field",
+                       "%qT has %u fields",
+                       type2, vec.length ());
+           }
+         return false;
+       }
+
          for (field1 = TYPE_FIELDS (type1); field1; field1 = DECL_CHAIN 
(field1))
        {
          if (TREE_CODE (field1) != FIELD_DECL)
@@ -1961,13 +2040,32 @@ layout_compatible_type_p (tree type1, tree type2)
              break;
            }
          if (j == vec.length ())
-           return false;
+           {
+             if (explain)
+               {
+                 inform (DECL_SOURCE_LOCATION (field1),
+                         "%qT has no member corresponding to %qD",
+                         type2, field1);
+                 inform (location_of (type2), "%qT declared here", type2);
+               }
+             return false;
+           }
          vec.unordered_remove (j);
        }
-      return true;
        }
-  return same_type_p (type1, type2);
+  if (!tree_int_cst_equal (TYPE_SIZE (type1), TYPE_SIZE (type2)))
+    {
+      if (explain)
+       {
+         inform (location_of (type1), "%qT and %qT have different sizes",
+                 type1, type2);
+         inform (location_of (type2), "%qT declared here", type2);
+       }
+      return false;
+    }
+
+  return true;
    }
    /* Returns 1 if TYPE1 is at least as qualified as TYPE2.  */
diff --git a/gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C 
b/gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C
new file mode 100644
index 00000000000..7ee5cbd6d07
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C
@@ -0,0 +1,86 @@
+// Test for diagnostics on failed is_layout_compatible.
+// { dg-do compile { target c++20 } }
+
+template <typename T, typename U>
+constexpr bool is_layout_compatible_v = __is_layout_compatible (T, U);
+
+static_assert(is_layout_compatible_v<int, unsigned>);  // { dg-error "assert" }
+// { dg-message "is not layout compatible" "" { target *-*-* } .-1 }
+// { dg-message "same type" "" { target *-*-* } .-2 }
+
+struct S {};
+static_assert(is_layout_compatible_v<const S, volatile int>);  // { dg-error 
"assert" }
+// { dg-message "is not layout compatible" "" { target *-*-* } .-1 }
+// { dg-message "same type" "" { target *-*-* } .-2 }
+
+struct A {
+  int a;
+  char b;  // { dg-message "'A::b' and 'B::b' do not correspond" }
+};
+struct B {
+  int a;
+  signed char b;  // { dg-message "declared here" }
+};
+static_assert(is_layout_compatible_v<A, B>);  // { dg-error "assert" }
+
+struct C {
+  int : 1;
+  int c : 7;
+  int : 0;  // { dg-message "'C::<anonymous>' and 'D::g' do not correspond" }
+  int : 2;
+};
+struct D {
+  int f : 1;

Hmm, I'm surprised that we consider named and unnamed bit-fields as
corresponding, but I guess the spec doesn't say otherwise.

+  int : 7;
+  int g : 2;  // { dg-message "declared here" }
+};
+static_assert(is_layout_compatible_v<C, D>);  // { dg-error "assert" }
+
+struct E {  // { dg-message "'E' is not a standard-layout type" }
+  int a;
+private:
+  int b;
+};
+struct F {
+  int a;
+private:
+  int b;
+};
+static_assert(is_layout_compatible_v<E, F>);  // { dg-error "assert" }
+
+union G {
+  int a;
+  long long b;
+  signed char c;  // { dg-message "'H' has no member corresponding to 'G::c'" }
+};
+union H {  // { dg-message "declared here" }
+  char x;
+  int y;
+  long long z;
+};
+static_assert(is_layout_compatible_v<G, H>);  // { dg-error "assert" }
+
+union I {  // { dg-message "'I' has 2 fields, but" }
+  int a;
+  double b;
+};
+union J {  // { dg-message "'J' has 1 field" }
+  int c;
+};
+static_assert(is_layout_compatible_v<I, J>);  // { dg-error "assert" }
+
+enum K : int {  // { dg-message "the underlying type of 'K' is 'int'" }
+  K0, K1
+};
+enum L : long int {  // { dg-message "the underlying type of 'L' is 'long 
int'" }
+  L0, L1
+};
+static_assert(is_layout_compatible_v<K, L>);  // { dg-error "assert" }
+
+struct M {  // { dg-message "different sizes" }

I think it would be clearer to refer to the alignment requirements.

Jason


The standard isn't really explicit about behaviour of different
alignment requirements for the top-level type, that I can see; but I
think it does make sense that differently sized types should not be
considered layout-compatible, and so this clarifies the note for when
the different sizes are caused by alignment differences.

But it does require that corresponding members have the same alignment
requirements, and considering the alignment of the type as a whole seems
natural; the alignment requirement for the type is effectively an alignment
requirement for the first member.

So I'd prefer to just talk about the alignment if that's different, without
mentioning that it affects the size.


To clarify, keep the logic as-is and just change the message, or do
something like this and actually change the checks?

   if (TYPE_ALIGN (type1) != TYPE_ALIGN (type2))
     {
       if (explain)
        {
          inform (location_of (type1),
                  "%qT and %qT have different alignment requirements",
                  type1, type2);
          inform (location_of (type2), "%qT declared here", type2);
        }
       return false;
     }

   if (!tree_int_cst_equal (TYPE_SIZE (type1), TYPE_SIZE (type2)))
     {
       if (explain)
        {
          inform (location_of (type1),
                  "%qT and %qT have different sizes", type1, type2);
          inform (location_of (type2), "%qT declared here", type2);
        }
       return false;
     }

This will change the behaviour of whether say

   struct A { char x[8]; };
   struct alignas(8) B { char x[8]; };

are considered layout compatible; we currently say that they are but it
sounds like maybe we want to interpret them as not being so?

That seemed intuitive to me from the words "layout-compatible", since they have different layout effects when used as member types.

But the actual wording seems pretty clear that we only consider the members, ignoring both alignment and size of the class itself. And Clang and MSVC seem to follow that, unlike current GCC: https://godbolt.org/z/9GYYY9e7z

So on reflection I think rather than add an alignment comparison, we want to drop the size comparison. That should probably be a separate patch from this diagnostic improvement, though.

Jason

Reply via email to