> 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