https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127655

            Bug ID: 127655
           Summary: ICE (and wrong note) explaining why a union's
                    assignment operator is defaulted-as-deleted
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: arthur.j.odwyer at gmail dot com
  Target Milestone: ---

Bug #118201 might be related.

// https://godbolt.org/z/M1d49j4c8
struct Cat {};
struct Leopard : Cat {
  int spots_;
  Leopard& operator=(Leopard&) = delete;
  using Cat::operator=;
};
union U {
  Leopard l;
  int i;
  U& operator=(const U&) noexcept = default;
};
void test(U& a) { a = a; }

GCC trunk (the rising GCC 17), with assertions enabled, says...

<source>:12:8: warning: explicitly defaulted copy assignment operator is
implicitly deleted because its declared type does not match the type of an
implicit copy assignment operator [-Wdefaulted-function-deleted]
   12 |     U& operator=(const U&) noexcept = default;
      |        ^~~~~~~~
<source>:12:8: note: expected signature: 'U& U::operator=(U&)'
<source>: In function 'void test(U&)':
<source>:14:23: error: use of deleted function 'U& U::operator=(const U&)'
   14 | void test(U& a) { a = a; }
      |                       ^
<source>:12:8: internal compiler error: in maybe_explain_implicit_delete, at
cp/method.cc:3366
   12 |     U& operator=(const U&) noexcept = default;
      |        ^~~~~~~~
0x2c6fea8 diagnostics::context::diagnostic_impl(rich_location*,
diagnostics::metadata const*, diagnostics::option_id, char const*,
__va_list_tag (*) [1], diagnostics::kind)
        ???:0
0x2c64aeb internal_error(char const*, ...)
        ???:0
0xb81e1c fancy_abort(char const*, int, char const*)
        ???:0
0xca6c97 mark_used(tree_node*, int)
        ???:0
0xbbeaba build_new_op(op_location_t const&, tree_code, int, tree_node*,
tree_node*, tree_node*, tree_node*, tree_node**, int)
        ???:0
0xee27fe cp_build_modify_expr(unsigned long, tree_node*, tree_code, tree_node*,
int)
        ???:0
0xee3047 build_x_modify_expr(unsigned long, tree_node*, tree_code, tree_node*,
tree_node*, int)
        ???:0
0xddb113 c_parse_file()
        ???:0
0xf6ec99 c_common_parse_file()
        ???:0
/cefs/cf/cfe4e183cf1d81e4a52c0aa7_gcc-trunk-20260928/bin/../libexec/gcc/x86_64-linux-gnu/17.0.0/cc1plus
-quiet -imultiarch x86_64-linux-gnu -iprefix
/cefs/cf/cfe4e183cf1d81e4a52c0aa7_gcc-trunk-20260928/bin/../lib/gcc/x86_64-linux-gnu/17.0.0/
-D_GNU_SOURCE <source> -quiet -dumpdir /app/ -dumpbase output.cpp -dumpbase-ext
.cpp -mtune=generic -march=x86-64 -g -std=c++26 -fdiagnostics-color=always
-fno-verbose-asm -o /app/output.s
Please submit a full bug report, with preprocessed source (by using
-freport-bug).
Please include the complete backtrace with any bug report.
See <https://gcc.gnu.org/bugs/> for instructions.


The "noexcept" in the reproducer is significant. If you remove it, then you
have to go all the way back to GCC 11.2 to make it ICE.
https://godbolt.org/z/qn8xoGb9a


Starting in GCC 11.3, the version without an explicit `noexcept` will bogusly
diagnose as follows:

<source>:14:23: error: use of deleted function 'U& U::operator=(const U&)'
   14 | void test(U& a) { a = a; }
      |                       ^
<source>:12:8: note: 'U& U::operator=(const U&)' is implicitly deleted because
its exception-specification does not match the implicit exception-specification
'noexcept'
   12 |     U& operator=(const U&) = default;
      |        ^~~~~~~~

It kind of smells like the "explainer" subroutine must have a laundry list of
reasons an operator might be defaulted-as-deleted, and when it gets near the
end of the list the excuses start getting wacky, and when it *falls off* the
end of the list it ICEs. The true reason in this case is *very* arcane: it's
that Leopard's copy assignment operator is "trivial for Cat but not trivial for
Leopard," and C++ currently has no way to express that.

IMHO GCC is correct in this case to define U::operator= as deleted. It just
needs to chill out when it fails to explain *why* U::operator= is deleted,
that's all.

(Btw, `Leopard` comes from my 2024 blog post
https://quuxplusone.github.io/blog/2024/05/15/false-advertising/ . I ran into
this bug while experimenting with the idea that you can detect various kinds of
triviality by putting things into unions.)
  • [Bug c++/127655] New: ICE (a... arthur.j.odwyer at gmail dot com via Gcc-bugs

Reply via email to