On 7/10/26 12:44 PM, Joyee Cheung wrote:
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))

It occurred to me that this is missing a check on intype, and I wondered if it would lead to wrong overload resolution for conversion from a class type like

struct A {
  constexpr operator E() { return a; }
  constexpr operator int() { return b; }
};

This doesn't actually seem to fail, but I still think it's safer to add the exact conditions from the text above. So this is what I'm pushing:

Thanks!

Jason
From 1b3eed48c824d102de0c0c24652610043ec18a27 Mon Sep 17 00:00:00 2001
From: Joyee Cheung <[email protected]>
Date: Fri, 10 Jul 2026 14:14:46 -0400
Subject: [PATCH] c++: Fix conversion to enum with fixed bool underlying type
 [PR96496]
To: [email protected]

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                                 |  26 ++++-
 gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C   | 109 ++++++++++++++++++
 .../g++.dg/warn/Wconversion-enum-fixed.C      |  24 ++++
 .../warn/Wint-in-bool-context-bool-enum.C     |  13 +++
 4 files changed, 168 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..a378b51a2be 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 00000000000..92196d0e688
--- /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 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.55.0

Reply via email to