CHECK_EQUAL: fix array != array compare under C++26 - #1886
Conversation
C++26 [over.match.oper]/p10 makes array == array (and array != array)
ill-formed. CHECK_EQUAL_LOCATION expanded its two side-effect-detection
probes literally as (operand) != (operand). As soon as either operand
was an array (most often a string literal in CHECK_EQUAL("text", x)),
the inner self-compare became a forbidden array-vs-array compare. The
same pattern is also a -Wdeprecated-array-compare error under C++20+
when -Werror is on.
Add a CppUTestPrivate::checkEqualSelfNotEqual<T>(T, T) helper that
takes both arguments by value, so any array decays to a pointer
before the comparison runs. The two side-effect probes
((actual) != (actual) and (expected) != (expected)) are routed
through it. Both arguments come from the same operand expression, so
T is identical on both sides; -Wsign-compare cannot fire.
The outer (expected) != (actual) is intentionally left at the macro
call site. Routing it through a function template would lose
-Wsign-compare's constant-fits exception that lets idioms like
CHECK_EQUAL(0, some_unsigned) build clean today.
Behavior is unchanged for every non-array operand. For arrays, the
resulting pointer-address self-compare matches the historical C++03
behavior (always false, since the same array's decayed pointer
equals itself).
Add a regression test exercising the array-operand path. Without the
helper, the test TU is ill-formed under C++26 and emits
-Wdeprecated-array-compare under C++20+; with the helper it compiles
at every standard from C++98 through C++26.
|
Thanks for the PR. Since in CppUTest we don't use templates, I'll need to look for an alternative solution. But, I verified the problem. |
|
I've checked this further. As we will not add templated code to CppUTest, this means there is no fix for this (afaik). However, the issue can be fixed relative easily by adding a cast in the call. So, I'm leaning towards just fixing this in the call towards CHECK_EQUAL and not in CppUTest itself. This should be easy as it gives a very explicit compiler error. Will that be ok with you? Thanks for the PR anyways... ps. I did added C++26 support in the build so we can find incompatibilities as early as possible. |
1 similar comment
|
I've checked this further. As we will not add templated code to CppUTest, this means there is no fix for this (afaik). However, the issue can be fixed relative easily by adding a cast in the call. So, I'm leaning towards just fixing this in the call towards CHECK_EQUAL and not in CppUTest itself. This should be easy as it gives a very explicit compiler error. Will that be ok with you? Thanks for the PR anyways... ps. I did added C++26 support in the build so we can find incompatibilities as early as possible. |
Replace the function template added in the previous commit with a
StringFrom-based probe, per review feedback: CppUTest uses no templates
anywhere in include/ or src/, and the helper was the sole exception.
The two side-effect-detection probes now compare the string renderings
of two separate evaluations of the operand:
if (StringFrom(actual) != StringFrom(actual))
if (StringFrom(expected) != StringFrom(expected))
Arrays decay to pointers at the call boundary, so no array-to-array
comparison is ever formed. This imposes no new requirement on operand
types: CHECK_EQUAL already documents that it "needs the operator!=(),
and a StringFrom(YourType) function", and the failure path already
calls StringFrom on both operands to build the message.
The operand is still evaluated twice, so multiple-evaluation detection
is preserved. The detection now compares string renderings rather than
values, so an operand whose two evaluations differ only beyond
StringFrom's default precision (e.g. a double past 6 digits) no longer
warns. That only suppresses an advisory message on an assertion that is
already failing.
The outer (expected) != (actual) remains at the macro call site, so
CHECK_EQUAL with two array operands is still ill-formed under C++26.
That case is user code writing a deprecated comparison directly; the
defect being fixed is CppUTest synthesizing one the caller never wrote.
Verified at C++98/23 (g++-13), C++26 (g++-14, g++-16), all with
-Wall -Wextra -Werror: builds clean, full suite passes. Restoring the
original macro fails as expected -- -Werror=array-compare on g++-13 at
C++20+, and "not allowed in C++26" on g++-16.
Note for reproducing the g++-16 run: it additionally requires
-Wno-keyword-macro, because g++-16 rejects MemoryLeakDetectorNewMacros.h:97
with "keyword 'new' defined as macro". That is pre-existing and unrelated
to this change, but it will block any future C++26 CI job.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Good catch. Replaced it with a if (StringFrom(actual) != StringFrom(actual))Arrays decay to pointers at the call boundary, so no array-to-array comparison is formed, and Verified at C++98/23/26 with |
|
I just noticed your second reply. I've offered a partial solution, do with it whatever you like. I no longer have the need I originally had for this change. |
|
The old solution uses the operator != and it is documented to expect that. Doing the comparison with StringFrom breaks that, hence I do not prefer that (also it will make it a lot slower). I'll be not accepting this PR and instead expect it to be fixed at the call rather than in CppUTest |
Problem
C++26 [over.match.oper]/p10 makes
array == array(andarray != array) ill-formed.CHECK_EQUAL_LOCATIONexpanded its two side-effect-detection probes literally as(operand) != (operand). As soon as either operand was an array — most commonly a string literal inCHECK_EQUAL("text", x)— the inner self-compare became a forbidden array-vs-array compare.The same pattern is also a
-Wdeprecated-array-compareerror under C++20+ when-Werroris on. CppUTest's existing tests don't exercise the array-operand path, so this never showed up in CI; it surfaces immediately in any consuming project that does.Fix
A small
CppUTestPrivate::checkEqualSelfNotEqual<T>(T, T)helper that takes both arguments by value, so any array decays to a pointer before the comparison runs. The two side-effect probes ((actual) != (actual)and(expected) != (expected)) route through it. Both arguments come from the same operand expression, soTis identical on both sides —-Wsign-comparecannot fire.The outer
(expected) != (actual)is intentionally left at the macro call site. Routing it through a function template would lose-Wsign-compare's constant-fits exception that lets idioms likeCHECK_EQUAL(0, some_unsigned)build clean today.Behavior is unchanged for every non-array operand. For arrays, the resulting pointer-address self-compare matches the historical C++03 behavior (always false, since the same array's decayed pointer equals itself).
Test
Added
CHECK_EQUAL_compilesWithArrayOperandinTestUTestMacro.cpp. Without the fix, the test TU emits-Wdeprecated-array-compareunder C++20+ with-Werror, and is ill-formed at C++26 regardless of warning flags. With the fix it compiles at every standard from C++98 through C++26.CI coverage
Verified by the existing
-Wall -Werrorjobs (Make Defaults, CMake Linux GNU 17/20/23, etc.) —-Wdeprecated-array-compareis on by default at C++20+ and gates the relevant condition. A future PR could add a C++26 job once compiler C++26 support stabilizes; this fix doesn't depend on that addition.Compatibility
Works at C++98 through C++26. No new headers included. The helper is a plain function template — no
<type_traits>, noif constexpr, no forwarding refs.