https://gcc.gnu.org/g:ca3810f001338bb4d15da43160e017d453f234e8
commit r17-2358-gca3810f001338bb4d15da43160e017d453f234e8 Author: Joyee Cheung <[email protected]> Date: Mon Jul 13 11:35:47 2026 -0400 c++: Fix conversion to enum with fixed bool underlying type [PR96496] For an enumeration with a fixed underlying type, [expr.static.cast]/8 requires the operand to be converted to the underlying type first, and then to the enumeration type. Previously, ocp_convert would convert the operand directly to the enumeration type via convert_to_integer_maybe_fold. For non-bool underlying types this gives the required value, but for an underlying type of bool it truncates the operand to the enum's 1-bit precision, keeping only the low bit and contradicting [conv.bool]: e.g. converting 2 yielded the enum value corresponding to false. This patch fixes it by following the specification and converting operands with a fixed underlying type to that type first, then to the enumeration type. For bool this routes the operand through the existing boolean-handling code with its truth value conversion, so it now goes through -Wint-in-bool-context diagnostics just like a cast to bool. The conversion now no longer emits -Wconversion if the operand is out of range since the value is converted first through well-defined conversions, consistent with explicit casts to the underlying type itself. This patch also includes tests for the changed warning behavior. PR c++/96496 gcc/cp/ChangeLog: * cvt.cc (ocp_convert): For an enumeration type with a fixed underlying type, convert the operand to that type first, as specified by [expr.static.cast]/8. Update the comment quoting [expr.static.cast]/8. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/enum-bool-conv.C: New test. * g++.dg/warn/Wconversion-enum-fixed.C: New test. * g++.dg/warn/Wint-in-bool-context-bool-enum.C: New test. Signed-off-by: Joyee Cheung <[email protected]> Diff: --- gcc/cp/cvt.cc | 26 ++++- gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C | 109 +++++++++++++++++++++ gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C | 24 +++++ .../g++.dg/warn/Wint-in-bool-context-bool-enum.C | 13 +++ 4 files changed, 168 insertions(+), 4 deletions(-) diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc index b18f66e582f9..a378b51a2bed 100644 --- a/gcc/cp/cvt.cc +++ b/gcc/cp/cvt.cc @@ -838,10 +838,28 @@ ocp_convert (tree type, tree expr, int convtype, int flags, /* [expr.static.cast] 8. A value of integral or enumeration type can be explicitly - converted to an enumeration type. The value is unchanged if - the original value is within the range of the enumeration - values. Otherwise, the resulting enumeration value is - unspecified. */ + converted to a complete enumeration type. If the enumeration + type has a fixed underlying type, the value is first converted + to that type by integral promotion or integral conversion, if + necessary, and then to the enumeration type. If the + enumeration type does not have a fixed underlying type, the + value is unchanged if the original value is within the range + of the enumeration values, and otherwise, the behavior is + undefined. A value of floating-point type can also be + explicitly converted to an enumeration type. The resulting + value is the same as converting the original value to the + underlying type of the enumeration, and subsequently to the + enumeration type. */ + if ((ENUM_FIXED_UNDERLYING_TYPE_P (type) + && INTEGRAL_OR_ENUMERATION_TYPE_P (intype)) + || SCALAR_FLOAT_TYPE_P (intype)) + { + e = ocp_convert (ENUM_UNDERLYING_TYPE (type), e, convtype, + flags, complain); + if (e == error_mark_node) + return error_mark_node; + } + tree val = fold_for_warn (e); if ((complain & tf_warning) && TREE_CODE (val) == INTEGER_CST diff --git a/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C b/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C new file mode 100644 index 000000000000..92196d0e688b --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C @@ -0,0 +1,109 @@ +// PR c++/96496 +// { dg-do run { target c++11 } } +// Conversion to an enum with a fixed underlying type of bool should convert +// the value to bool first ([expr.static.cast]/8, CWG 2338). + +enum Flag : bool { kNo, kYes }; +enum class ScopedFlag : bool { kOff, kOn }; + +struct S { + constexpr operator Flag () const { return kYes; } + operator bool() = delete; // but not using this conversion function +}; + +template <typename T> struct A +{ + enum E : T { kZero, kOne }; +}; + +static_assert (static_cast<Flag> (2) == kYes, ""); +static_assert (static_cast<Flag> (-1) == kYes, ""); +static_assert ((Flag) 2 == kYes, ""); +static_assert (static_cast<Flag> (true) == kYes, ""); +static_assert (static_cast<Flag> (false) == kNo, ""); +static_assert (static_cast<Flag> (0) == kNo, ""); +static_assert (static_cast<Flag> (1) == kYes, ""); +static_assert (static_cast<Flag> (0.5) == kYes, ""); +static_assert (static_cast<Flag> (0.0) == kNo, ""); +static_assert (static_cast<ScopedFlag> (6) == ScopedFlag::kOn, ""); +static_assert (static_cast<Flag> (ScopedFlag::kOn) == kYes, ""); +static_assert (static_cast<Flag> (S{}) == kYes, ""); +static_assert (static_cast<A<bool>::E> (6) == A<bool>::kOne, ""); +static_assert (static_cast<A<char>::E> (6) == 6, ""); + +// Helpers to keep the operands non-constant and exercise the +// runtime conversion. + +Flag +convert (int x) +{ + return static_cast<Flag> (x); +} + +Flag +convert (double d) +{ + return static_cast<Flag> (d); +} + +Flag +convert_cstyle (int x) +{ + return (Flag) x; +} + +Flag +convert_from_bool (bool b) +{ + return static_cast<Flag> (b); +} + +ScopedFlag +convert_scoped (int x) +{ + return static_cast<ScopedFlag> (x); +} + +A<bool>::E +convert_dep_bool (int x) +{ + return static_cast<A<bool>::E> (x); +} + +A<char>::E +convert_dep_char (int x) +{ + return static_cast<A<char>::E> (x); +} + +Flag +convert_from_scoped (ScopedFlag f) +{ + return static_cast<Flag> (f); +} + +Flag +convert_via_operator (S s) +{ + return static_cast<Flag> (s); +} + +int +main () +{ + if (convert (2) != kYes) __builtin_abort (); + if (convert (-1) != kYes) __builtin_abort (); + if (convert (0) != kNo) __builtin_abort (); + if (convert (1) != kYes) __builtin_abort (); + if (convert (0.5) != kYes) __builtin_abort (); + if (convert (0.0) != kNo) __builtin_abort (); + if (convert_cstyle (2) != kYes) __builtin_abort (); + if (convert_cstyle (0) != kNo) __builtin_abort (); + if (convert_from_bool (true) != kYes) __builtin_abort (); + if (convert_from_bool (false) != kNo) __builtin_abort (); + if (convert_scoped (6) != ScopedFlag::kOn) __builtin_abort (); + if (convert_from_scoped (ScopedFlag::kOn) != kYes) __builtin_abort (); + if (convert_via_operator (S{}) != kYes) __builtin_abort (); + if (convert_dep_bool (6) != A<bool>::kOne) __builtin_abort (); + if (convert_dep_char (6) != 6) __builtin_abort (); +} diff --git a/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C b/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C new file mode 100644 index 000000000000..a72e232f16d5 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C @@ -0,0 +1,24 @@ +// PR c++/96496 +// { dg-do compile { target c++11 } } +// { dg-options "-Wconversion" } +// Conversion to an enum with a fixed underlying type is well-defined +// ([expr.static.cast]/8, CWG 2338), so -Wconversion should not warn. + +enum Flag : bool { kNo, kYes }; +enum C2 : unsigned char { C0 }; +enum SC : signed char { SC0 }; +enum class SI : short { SI0 }; + +int f1 () { return (Flag) 6; } // { dg-bogus "outside the range" } +int f2 () { return (C2) -1; } // { dg-bogus "outside the range" } +int f3 () { return (C2) 257; } // { dg-bogus "outside the range" } +int f4 () { return (SC) 128; } // { dg-bogus "outside the range" } +int f5 () { return (int) (SI) 65537; } // { dg-bogus "outside the range" } +int f6 (double d) { return (Flag) d; } // { dg-bogus "conversion" } +int f7 () { return (Flag) 2.5; } // { dg-bogus "conversion" } + +// The conversion gives the same value as converting to the underlying type. +static_assert (static_cast<C2> (257) == static_cast<unsigned char> (257), ""); +static_assert (static_cast<SC> (128) == static_cast<signed char> (128), ""); +static_assert (static_cast<SI> (65537) + == static_cast<SI> (static_cast<short> (65537)), ""); diff --git a/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C b/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C new file mode 100644 index 000000000000..c15faf12f607 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C @@ -0,0 +1,13 @@ +// PR c++/96496 +// { dg-do compile { target c++11 } } +// { dg-options "-Wint-in-bool-context" } +// For static_cast<Flag>(expr), -Wint-in-bool-context applies similarly +// to static_cast<bool>(expr). + +enum Flag : bool { kNo, kYes }; + +Flag f1 (int x) { return static_cast<Flag> (x << 1); } // { dg-warning "in boolean context" } +Flag f2 (int x, int y) { return static_cast<Flag> (x * y); } // { dg-warning "in boolean context" } +Flag f3 (bool c) { return static_cast<Flag> (c ? 2 : 3); } // { dg-warning "in boolean context" } +Flag f4 (int x) { return static_cast<Flag> (x); } // { dg-bogus "in boolean context" } +Flag f5 (int x) { return static_cast<Flag> (x != 0); } // { dg-bogus "in boolean context" }
