Add enum types to what is captured by value by default

As it turns out, enums can be used to declare bitfields, and we
cannot form a reference to a bitfield in the cpature. Thus, we add
`std::is_enum` as a criteria to the default for `capture_by_value`,
so that enum-based bitfields are also captured by value and thus
decomposable.

Closes #3001
This commit is contained in:
Martin Hořeňovský
2025-07-29 23:51:13 +02:00
parent a1c7ee115f
commit ccabd4de89
20 changed files with 164 additions and 29 deletions

View File

@@ -78,10 +78,11 @@
*
* 3) If a type has no linkage, we also cannot capture it by reference.
* The solution is once again to capture them by value. We handle
* the common cases by using `std::is_arithmetic` as the default
* for `Catch::capture_by_value`, but that is only a some-effort
* heuristic. But as with 2), users can specialize `capture_by_value`
* for their own types as needed.
* the common cases by using `std::is_arithmetic` and `std::is_enum`
* as the default for `Catch::capture_by_value`, but that is only a
* some-effort heuristic. These combine to capture all possible bitfield
* bases, and also some trait-like types. As with 2), users can
* specialize `capture_by_value` for their own types as needed.
*
* 4) To support C++20 and make the SFINAE on our decomposing operators
* work, the SFINAE has to happen in return type, rather than in
@@ -133,13 +134,22 @@ namespace Catch {
using RemoveCVRef_t = std::remove_cv_t<std::remove_reference_t<T>>;
}
// Note: There is nothing that stops us from extending this,
// e.g. to `std::is_scalar`, but the more encompassing
// traits are usually also more expensive. For now we
// keep this as it used to be and it can be changed later.
// Note: This is about as much as we can currently reasonably support.
// In an ideal world, we could capture by value small trivially
// copyable types, but the actual `std::is_trivially_copyable`
// trait is a huge mess with standard-violating results on
// GCC and Clang, which are unlikely to be fixed soon due to ABI
// concerns.
// `std::is_scalar` also causes issues due to the `is_pointer`
// component, which causes ambiguity issues with (references-to)
// function pointer. If those are resolved, we still need to
// disambiguate the overload set for arrays, through explicit
// overload for references to sized arrays.
template <typename T>
struct capture_by_value
: std::integral_constant<bool, std::is_arithmetic<T>{}> {};
: std::integral_constant<bool,
std::is_arithmetic<T>::value ||
std::is_enum<T>::value> {};
#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS )
template <>

View File

@@ -26,6 +26,7 @@ Nor would this
:test-result: PASS #2152 - ULP checks between differently signed values were wrong - double
:test-result: PASS #2152 - ULP checks between differently signed values were wrong - float
:test-result: XFAIL #2615 - Throwing in constructor generator fails test case but does not abort
:test-result: PASS #3001: Enum-based bitfields can be captured
:test-result: XFAIL #748 - captures with unexpected exceptions
:test-result: PASS #809
:test-result: PASS #833

View File

@@ -24,6 +24,7 @@
:test-result: PASS #2152 - ULP checks between differently signed values were wrong - double
:test-result: PASS #2152 - ULP checks between differently signed values were wrong - float
:test-result: XFAIL #2615 - Throwing in constructor generator fails test case but does not abort
:test-result: PASS #3001: Enum-based bitfields can be captured
:test-result: XFAIL #748 - captures with unexpected exceptions
:test-result: PASS #809
:test-result: PASS #833

View File

@@ -85,6 +85,8 @@ Matchers.tests.cpp:<line number>: passed: smallest_non_zero, !WithinULP( -smalle
Matchers.tests.cpp:<line number>: passed: smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45])
Matchers.tests.cpp:<line number>: passed: smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00])
Generators.tests.cpp:<line number>: failed: unexpected exception with message: 'failure to init'; expression was: {Unknown expression after the reported line}
Compilation.tests.cpp:<line number>: passed: bf.e == 1 for: 1 == 1
Compilation.tests.cpp:<line number>: passed: 1 == bf.e for: 1 == 1
Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'expected exception'; expression was: {Unknown expression after the reported line}
Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'answer := 42'; expression was: thisThrows() with 1 message: 'expected exception'
Exception.tests.cpp:<line number>: passed: thisThrows() with 1 message: 'answer := 42'
@@ -2869,7 +2871,7 @@ InternalBenchmark.tests.cpp:<line number>: passed: med == 18. for: 18.0 == 18.0
InternalBenchmark.tests.cpp:<line number>: passed: q3 == 23. for: 23.0 == 23.0
Misc.tests.cpp:<line number>: passed:
Misc.tests.cpp:<line number>: passed:
test cases: 431 | 313 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2288 | 2090 passed | 157 failed | 41 failed as expected
test cases: 432 | 314 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2290 | 2092 passed | 157 failed | 41 failed as expected

View File

@@ -83,6 +83,8 @@ Matchers.tests.cpp:<line number>: passed: smallest_non_zero, !WithinULP( -smalle
Matchers.tests.cpp:<line number>: passed: smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45])
Matchers.tests.cpp:<line number>: passed: smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00])
Generators.tests.cpp:<line number>: failed: unexpected exception with message: 'failure to init'; expression was: {Unknown expression after the reported line}
Compilation.tests.cpp:<line number>: passed: bf.e == 1 for: 1 == 1
Compilation.tests.cpp:<line number>: passed: 1 == bf.e for: 1 == 1
Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'expected exception'; expression was: {Unknown expression after the reported line}
Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'answer := 42'; expression was: thisThrows() with 1 message: 'expected exception'
Exception.tests.cpp:<line number>: passed: thisThrows() with 1 message: 'answer := 42'
@@ -2858,7 +2860,7 @@ InternalBenchmark.tests.cpp:<line number>: passed: med == 18. for: 18.0 == 18.0
InternalBenchmark.tests.cpp:<line number>: passed: q3 == 23. for: 23.0 == 23.0
Misc.tests.cpp:<line number>: passed:
Misc.tests.cpp:<line number>: passed:
test cases: 431 | 313 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2288 | 2090 passed | 157 failed | 41 failed as expected
test cases: 432 | 314 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2290 | 2092 passed | 157 failed | 41 failed as expected

View File

@@ -1719,6 +1719,6 @@ due to unexpected exception with message:
Why would you throw a std::string?
===============================================================================
test cases: 431 | 331 passed | 76 failed | 7 skipped | 17 failed as expected
assertions: 2267 | 2090 passed | 136 failed | 41 failed as expected
test cases: 432 | 332 passed | 76 failed | 7 skipped | 17 failed as expected
assertions: 2269 | 2092 passed | 136 failed | 41 failed as expected

View File

@@ -772,6 +772,22 @@ Generators.tests.cpp:<line number>: FAILED:
due to unexpected exception with message:
failure to init
-------------------------------------------------------------------------------
#3001: Enum-based bitfields can be captured
-------------------------------------------------------------------------------
Compilation.tests.cpp:<line number>
...............................................................................
Compilation.tests.cpp:<line number>: PASSED:
REQUIRE( bf.e == 1 )
with expansion:
1 == 1
Compilation.tests.cpp:<line number>: PASSED:
REQUIRE( 1 == bf.e )
with expansion:
1 == 1
-------------------------------------------------------------------------------
#748 - captures with unexpected exceptions
outside assertions
@@ -19184,6 +19200,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: PASSED:
===============================================================================
test cases: 431 | 313 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2288 | 2090 passed | 157 failed | 41 failed as expected
test cases: 432 | 314 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2290 | 2092 passed | 157 failed | 41 failed as expected

View File

@@ -770,6 +770,22 @@ Generators.tests.cpp:<line number>: FAILED:
due to unexpected exception with message:
failure to init
-------------------------------------------------------------------------------
#3001: Enum-based bitfields can be captured
-------------------------------------------------------------------------------
Compilation.tests.cpp:<line number>
...............................................................................
Compilation.tests.cpp:<line number>: PASSED:
REQUIRE( bf.e == 1 )
with expansion:
1 == 1
Compilation.tests.cpp:<line number>: PASSED:
REQUIRE( 1 == bf.e )
with expansion:
1 == 1
-------------------------------------------------------------------------------
#748 - captures with unexpected exceptions
outside assertions
@@ -19173,6 +19189,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: PASSED:
===============================================================================
test cases: 431 | 313 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2288 | 2090 passed | 157 failed | 41 failed as expected
test cases: 432 | 314 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2290 | 2092 passed | 157 failed | 41 failed as expected

View File

@@ -772,6 +772,22 @@ Generators.tests.cpp:<line number>: FAILED:
due to unexpected exception with message:
failure to init
-------------------------------------------------------------------------------
#3001: Enum-based bitfields can be captured
-------------------------------------------------------------------------------
Compilation.tests.cpp:<line number>
...............................................................................
Compilation.tests.cpp:<line number>: PASSED:
REQUIRE( bf.e == 1 )
with expansion:
1 == 1
Compilation.tests.cpp:<line number>: PASSED:
REQUIRE( 1 == bf.e )
with expansion:
1 == 1
-------------------------------------------------------------------------------
#748 - captures with unexpected exceptions
outside assertions
@@ -952,6 +968,6 @@ Condition.tests.cpp:<line number>: FAILED:
CHECK( true != true )
===============================================================================
test cases: 33 | 27 passed | 3 failed | 3 failed as expected
assertions: 102 | 94 passed | 4 failed | 4 failed as expected
test cases: 34 | 28 passed | 3 failed | 3 failed as expected
assertions: 104 | 96 passed | 4 failed | 4 failed as expected

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuitesloose text artifact
>
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2300" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2302" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties>
<property name="random-seed" value="1"/>
<property name="filters" value="&quot;*&quot; ~[!nonportable] ~[!benchmark] ~[approvals]"/>
@@ -60,6 +60,7 @@ failure to init
at Generators.tests.cpp:<line number>
</error>
</testcase>
<testcase classname="<exe-name>.global" name="#3001: Enum-based bitfields can be captured" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="#748 - captures with unexpected exceptions" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="#748 - captures with unexpected exceptions/outside assertions" time="{duration}" status="run">
<skipped message="TEST_CASE tagged with !mayfail"/>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2300" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2302" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties>
<property name="random-seed" value="1"/>
<property name="filters" value="&quot;*&quot; ~[!nonportable] ~[!benchmark] ~[approvals]"/>
@@ -59,6 +59,7 @@ failure to init
at Generators.tests.cpp:<line number>
</error>
</testcase>
<testcase classname="<exe-name>.global" name="#3001: Enum-based bitfields can be captured" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="#748 - captures with unexpected exceptions" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="#748 - captures with unexpected exceptions/outside assertions" time="{duration}" status="run">
<skipped message="TEST_CASE tagged with !mayfail"/>

View File

@@ -655,6 +655,7 @@ at Class.tests.cpp:<line number>
<testCase name="#1319: Sections can have description (even if it is not saved/SectionName" duration="{duration}"/>
<testCase name="#1403" duration="{duration}"/>
<testCase name="#1548" duration="{duration}"/>
<testCase name="#3001: Enum-based bitfields can be captured" duration="{duration}"/>
<testCase name="#809" duration="{duration}"/>
<testCase name="#833" duration="{duration}"/>
<testCase name="#872" duration="{duration}"/>

View File

@@ -654,6 +654,7 @@ at Class.tests.cpp:<line number>
<testCase name="#1319: Sections can have description (even if it is not saved/SectionName" duration="{duration}"/>
<testCase name="#1403" duration="{duration}"/>
<testCase name="#1548" duration="{duration}"/>
<testCase name="#3001: Enum-based bitfields can be captured" duration="{duration}"/>
<testCase name="#809" duration="{duration}"/>
<testCase name="#833" duration="{duration}"/>
<testCase name="#872" duration="{duration}"/>

View File

@@ -166,6 +166,10 @@ ok {test-number} - smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.
ok {test-number} - smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00])
# #2615 - Throwing in constructor generator fails test case but does not abort
not ok {test-number} - unexpected exception with message: 'failure to init'; expression was: {Unknown expression after the reported line}
# #3001: Enum-based bitfields can be captured
ok {test-number} - bf.e == 1 for: 1 == 1
# #3001: Enum-based bitfields can be captured
ok {test-number} - 1 == bf.e for: 1 == 1
# #748 - captures with unexpected exceptions
not ok {test-number} - unexpected exception with message: 'expected exception'; expression was: {Unknown expression after the reported line}
# #748 - captures with unexpected exceptions
@@ -4597,5 +4601,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0
ok {test-number} -
# xmlentitycheck
ok {test-number} -
1..2300
1..2302

View File

@@ -164,6 +164,10 @@ ok {test-number} - smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.
ok {test-number} - smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00])
# #2615 - Throwing in constructor generator fails test case but does not abort
not ok {test-number} - unexpected exception with message: 'failure to init'; expression was: {Unknown expression after the reported line}
# #3001: Enum-based bitfields can be captured
ok {test-number} - bf.e == 1 for: 1 == 1
# #3001: Enum-based bitfields can be captured
ok {test-number} - 1 == bf.e for: 1 == 1
# #748 - captures with unexpected exceptions
not ok {test-number} - unexpected exception with message: 'expected exception'; expression was: {Unknown expression after the reported line}
# #748 - captures with unexpected exceptions
@@ -4586,5 +4590,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0
ok {test-number} -
# xmlentitycheck
ok {test-number} -
1..2300
1..2302

View File

@@ -55,6 +55,8 @@
##teamcity[testStarted name='#2615 - Throwing in constructor generator fails test case but does not abort']
##teamcity[testIgnored name='#2615 - Throwing in constructor generator fails test case but does not abort' message='Generators.tests.cpp:<line number>|n...............................................................................|n|nGenerators.tests.cpp:<line number>|nunexpected exception with message:|n "failure to init"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n- failure ignore as test marked as |'ok to fail|'|n']
##teamcity[testFinished name='#2615 - Throwing in constructor generator fails test case but does not abort' duration="{duration}"]
##teamcity[testStarted name='#3001: Enum-based bitfields can be captured']
##teamcity[testFinished name='#3001: Enum-based bitfields can be captured' duration="{duration}"]
##teamcity[testStarted name='#748 - captures with unexpected exceptions']
##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|noutside assertions|n-------------------------------------------------------------------------------|nException.tests.cpp:<line number>|n...............................................................................|n|nException.tests.cpp:<line number>|nunexpected exception with message:|n "expected exception"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n- failure ignore as test marked as |'ok to fail|'|n']
##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|ninside REQUIRE_NOTHROW|n-------------------------------------------------------------------------------|nException.tests.cpp:<line number>|n...............................................................................|n|nException.tests.cpp:<line number>|nunexpected exception with messages:|n "answer := 42"|n "expected exception"|n REQUIRE_NOTHROW( thisThrows() )|nwith expansion:|n thisThrows()|n- failure ignore as test marked as |'ok to fail|'|n']

View File

@@ -55,6 +55,8 @@
##teamcity[testStarted name='#2615 - Throwing in constructor generator fails test case but does not abort']
##teamcity[testIgnored name='#2615 - Throwing in constructor generator fails test case but does not abort' message='Generators.tests.cpp:<line number>|n...............................................................................|n|nGenerators.tests.cpp:<line number>|nunexpected exception with message:|n "failure to init"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n- failure ignore as test marked as |'ok to fail|'|n']
##teamcity[testFinished name='#2615 - Throwing in constructor generator fails test case but does not abort' duration="{duration}"]
##teamcity[testStarted name='#3001: Enum-based bitfields can be captured']
##teamcity[testFinished name='#3001: Enum-based bitfields can be captured' duration="{duration}"]
##teamcity[testStarted name='#748 - captures with unexpected exceptions']
##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|noutside assertions|n-------------------------------------------------------------------------------|nException.tests.cpp:<line number>|n...............................................................................|n|nException.tests.cpp:<line number>|nunexpected exception with message:|n "expected exception"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n- failure ignore as test marked as |'ok to fail|'|n']
##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|ninside REQUIRE_NOTHROW|n-------------------------------------------------------------------------------|nException.tests.cpp:<line number>|n...............................................................................|n|nException.tests.cpp:<line number>|nunexpected exception with messages:|n "answer := 42"|n "expected exception"|n REQUIRE_NOTHROW( thisThrows() )|nwith expansion:|n thisThrows()|n- failure ignore as test marked as |'ok to fail|'|n']

View File

@@ -681,6 +681,25 @@ Nor would this
</Expression>
<OverallResult success="true" skips="0"/>
</TestCase>
<TestCase name="#3001: Enum-based bitfields can be captured" filename="tests/<exe-name>/UsageTests/Compilation.tests.cpp" >
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/UsageTests/Compilation.tests.cpp" >
<Original>
bf.e == 1
</Original>
<Expanded>
1 == 1
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/UsageTests/Compilation.tests.cpp" >
<Original>
1 == bf.e
</Original>
<Expanded>
1 == 1
</Expanded>
</Expression>
<OverallResult success="true" skips="0"/>
</TestCase>
<TestCase name="#748 - captures with unexpected exceptions" tags="[!shouldfail][!throws][.][failing]" filename="tests/<exe-name>/UsageTests/Exception.tests.cpp" >
<Section name="outside assertions" filename="tests/<exe-name>/UsageTests/Exception.tests.cpp" >
<Expression success="false" filename="tests/<exe-name>/UsageTests/Exception.tests.cpp" >
@@ -22182,6 +22201,6 @@ Approx( -1.95996398454005449 )
</Section>
<OverallResult success="true" skips="0"/>
</TestCase>
<OverallResults successes="2090" failures="157" expectedFailures="41" skips="12"/>
<OverallResultsCases successes="313" failures="95" expectedFailures="17" skips="6"/>
<OverallResults successes="2092" failures="157" expectedFailures="41" skips="12"/>
<OverallResultsCases successes="314" failures="95" expectedFailures="17" skips="6"/>
</Catch2TestRun>

View File

@@ -681,6 +681,25 @@ Nor would this
</Expression>
<OverallResult success="true" skips="0"/>
</TestCase>
<TestCase name="#3001: Enum-based bitfields can be captured" filename="tests/<exe-name>/UsageTests/Compilation.tests.cpp" >
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/UsageTests/Compilation.tests.cpp" >
<Original>
bf.e == 1
</Original>
<Expanded>
1 == 1
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/UsageTests/Compilation.tests.cpp" >
<Original>
1 == bf.e
</Original>
<Expanded>
1 == 1
</Expanded>
</Expression>
<OverallResult success="true" skips="0"/>
</TestCase>
<TestCase name="#748 - captures with unexpected exceptions" tags="[!shouldfail][!throws][.][failing]" filename="tests/<exe-name>/UsageTests/Exception.tests.cpp" >
<Section name="outside assertions" filename="tests/<exe-name>/UsageTests/Exception.tests.cpp" >
<Expression success="false" filename="tests/<exe-name>/UsageTests/Exception.tests.cpp" >
@@ -22181,6 +22200,6 @@ Approx( -1.95996398454005449 )
</Section>
<OverallResult success="true" skips="0"/>
</TestCase>
<OverallResults successes="2090" failures="157" expectedFailures="41" skips="12"/>
<OverallResultsCases successes="313" failures="95" expectedFailures="17" skips="6"/>
<OverallResults successes="2092" failures="157" expectedFailures="41" skips="12"/>
<OverallResultsCases successes="314" failures="95" expectedFailures="17" skips="6"/>
</Catch2TestRun>

View File

@@ -144,6 +144,23 @@ TEST_CASE("#1027: Bitfields can be captured") {
REQUIRE(0 == y.v);
}
TEST_CASE( "#3001: Enum-based bitfields can be captured" ) {
enum E {
ZERO = 0,
ONE = 1,
TWO = 2,
};
struct BF {
E e : 2;
};
BF bf{};
bf.e = ONE;
REQUIRE( bf.e == 1 );
REQUIRE( 1 == bf.e );
}
// Comparison operators can return non-booleans.
// This is unusual, but should be supported.
TEST_CASE("#1147") {