> On 10 Jul 2026, at 00:48, Jason Merrill <[email protected]> wrote:
>
> Yes, I agree that the existing code got the right result for non-bool
> underlying types, but it seems simpler to follow the letter of the standard
> with e.g.
> 
> +         if (ENUM_FIXED_UNDERLYING_TYPE_P (type))
> +           e = ocp_convert (ENUM_UNDERLYING_TYPE (type), e, convtype,
> +                            flags, complain);
> 
> right after the comment quoting that passage; then none of the other changes
> are needed.

Thanks for the suggestion, indeed it's much simpler. Done in v2.
This also made the explicit ENUM_FIXED_UNDERLYING_TYPE_P check in
the -Wconversion condition unnecessary, since the warning now checks
the already-converted value.

Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

-- >8 --

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]>
---
 gcc/cp/cvt.cc                                 |  24 +++-
 gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C   | 106 ++++++++++++++++++
 .../g++.dg/warn/Wconversion-enum-fixed.C      |  24 ++++
 .../warn/Wint-in-bool-context-bool-enum.C     |  13 +++
 4 files changed, 163 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C
 create mode 100644 gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C
 create mode 100644 gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C

diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc
index b18f66e582f..8952782a84c 100644
--- a/gcc/cp/cvt.cc
+++ b/gcc/cp/cvt.cc
@@ -838,10 +838,26 @@ 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))
+           {
+             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 00000000000..1e762295609
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C
@@ -0,0 +1,106 @@
+// 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; } };
+
+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 00000000000..a72e232f16d
--- /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 00000000000..c15faf12f60
--- /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" }
-- 
2.54.0

Reply via email to