1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
|
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#pragma once
#include <Progmasoft/Catch3/ToString.hpp>
#include <Progmasoft/Catch3/Internal/StringRef.hpp>
#include <Progmasoft/Catch3/Internal/CompareTraits.hpp>
#include <Progmasoft/Catch3/Internal/TestFailureException.hpp>
#include <Progmasoft/Catch3/Internal/LogicalTraits.hpp>
#include <Progmasoft/Catch3/Internal/CompilerCapabilities.hpp>
#include <type_traits>
#include <iosfwd>
/** \file
* Why does decomposing look the way it does:
*
* Conceptually, decomposing is simple. We change `REQUIRE( a == b )` into
* `Decomposer{} <= a == b`, so that `Decomposer{} <= a` is evaluated first,
* and our custom operator is used for `a == b`, because `a` is transformed
* into `ExprLhs<T&>` and then into `BinaryExpr<T&, U&>`.
*
* In practice, decomposing ends up a mess, because we have to support
* various fun things.
*
* 1) Types that are only comparable with literal 0, and they do this by
* comparing against a magic type with pointer constructor and deleted
* other constructors. Example: `REQUIRE((a <=> b) == 0)` in libstdc++
*
* 2) Types that are only comparable with literal 0, and they do this by
* comparing against a magic type with consteval integer constructor.
* Example: `REQUIRE((a <=> b) == 0)` in current MSVC STL.
*
* 3) Types that have no linkage, and so we cannot form a reference to
* them. Example: some implementations of traits.
*
* 4) Starting with C++20, when the compiler sees `a == b`, it also uses
* `b == a` when constructing the overload set. For us this means that
* when the compiler handles `ExprLhs<T> == b`, it also tries to resolve
* the overload set for `b == ExprLhs<T>`.
*
* To accommodate these use cases, decomposer ended up rather complex.
*
* 1) These types are handled by adding SFINAE overloads to our comparison
* operators, checking whether `T == U` are comparable with the given
* operator, and if not, whether T (or U) are comparable with literal 0.
* If yes, the overload compares T (or U) with 0 literal inline in the
* definition.
*
* Note that for extra correctness, we check that the other type is
* either an `int` (literal 0 is captured as `int` by templates), or
* a `long` (some platforms use 0L for `NULL` and we want to support
* that for pointer comparisons).
*
* 2) For these types, `is_foo_comparable<T, int>` is true, but letting
* them fall into the overload that actually does `T == int` causes
* compilation error. Handling them requires that the decomposition
* is `constexpr`, so that P2564R3 applies and the `consteval` from
* their accompanying magic type is propagated through the `constexpr`
* call stack.
*
* However this is not enough to handle these types automatically,
* because our default is to capture types by reference, to avoid
* runtime copies. While these references cannot become dangling,
* they outlive the constexpr context and thus the default capture
* path cannot be actually constexpr.
*
* The solution is to capture these types by value, by explicitly
* specializing `Catch::capture_by_value` for them. Catch2 provides
* specialization for `std::foo_ordering`s, but users can specialize
* the trait for their own types as well.
*
* 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` 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
* a template type. This is due to our use of logical type traits
* (`conjunction`/`disjunction`/`negation`), that we use to workaround
* an issue in older (9-) versions of GCC. I still blame C++20 for
* this, because without the comparison order switching, the logical
* traits could still be used in template type.
*
* There are also other side concerns, e.g. supporting both `REQUIRE(a)`
* and `REQUIRE(a == b)`, or making `REQUIRE_THAT(a, IsEqual(b))` slot
* nicely into the same expression handling logic, but these are rather
* straightforward and add only a bit of complexity (e.g. common base
* class for decomposed expressions).
*/
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4389) // '==' : signed/unsigned mismatch
#pragma warning(disable:4018) // more "signed/unsigned mismatch"
#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)
#pragma warning(disable:4180) // qualifier applied to function type has no meaning
#pragma warning(disable:4800) // Forcing result to true or false
#endif
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wsign-compare"
# pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#elif defined __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#endif
#if defined(CATCH_CPP20_OR_GREATER) && __has_include(<compare>)
# include <compare>
# if defined( __cpp_lib_three_way_comparison ) && \
__cpp_lib_three_way_comparison >= 201907L
# define CATCH_CONFIG_CPP20_COMPARE_OVERLOADS
# endif
#endif
namespace Catch {
namespace Detail {
// This was added in C++20, but we require only C++17.
template <typename T>
using RemoveCVRef_t = std::remove_cv_t<std::remove_reference_t<T>>;
}
// 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>::value ||
std::is_enum<T>::value> {};
#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS )
template <>
struct capture_by_value<std::strong_ordering> : std::true_type {};
template <>
struct capture_by_value<std::weak_ordering> : std::true_type {};
template <>
struct capture_by_value<std::partial_ordering> : std::true_type {};
#endif
template <typename T>
struct always_false : std::false_type {};
class ITransientExpression {
//! Type-erased streaming function for the derived expression
using StreamFn = void ( * )( void const*, std::ostream& );
StreamFn m_streamFn;
bool m_isBinaryExpression;
bool m_result;
public:
constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }
constexpr auto getResult() const -> bool { return m_result; }
constexpr ITransientExpression( StreamFn streamFn,
bool isBinaryExpression,
bool result ):
m_streamFn( streamFn ),
m_isBinaryExpression( isBinaryExpression ),
m_result( result ) {}
friend std::ostream& operator<<( std::ostream& out,
ITransientExpression const& expr ) {
expr.m_streamFn( &expr, out );
return out;
}
};
void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );
template<typename LhsT, typename RhsT>
class BinaryExpr final : public ITransientExpression {
LhsT m_lhs;
StringRef m_op;
RhsT m_rhs;
static void streamFn( void const* self, std::ostream& out ) {
auto const& expr = *static_cast<BinaryExpr const*>(
static_cast<ITransientExpression const*>( self ) );
formatReconstructedExpression(
out,
Catch::Detail::stringify( expr.m_lhs ),
expr.m_op,
Catch::Detail::stringify( expr.m_rhs ) );
}
public:
constexpr BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs )
: ITransientExpression{ &streamFn, true, comparisonResult },
m_lhs( lhs ),
m_op( op ),
m_rhs( rhs )
{}
};
#define CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( op ) \
template <typename LhsT, typename RhsT, typename T> \
auto operator op( BinaryExpr<LhsT, RhsT> const&, T ) \
->BinaryExpr<LhsT, RhsT const&> const { \
static_assert( always_false<T>::value, \
"chained comparisons are not supported inside " \
"assertions, wrap the expression inside " \
"parentheses, or decompose it" ); \
}
/*
* The operators are **intentionally** not hidden friends.
*
* If they were hidden friends, all of them would get stamped out when
* a `BinaryExpr` is instantiated with a new type. In practice, only
* few operators are used for each different `BinaryExpr<LhsT, RhsT>`,
* so this avoids bunch of wasted work.
*/
CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( && )
CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( || )
CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( == )
CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( != )
CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( > )
CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( < )
CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( >= )
CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR( <= )
#undef CATCH_INTERNAL_DEFINE_BINARY_EXPR_OPERATOR
template<typename LhsT>
class UnaryExpr final : public ITransientExpression {
LhsT m_lhs;
static void streamFn( void const* self, std::ostream& os ) {
auto const& expr = *static_cast<UnaryExpr const*>(
static_cast<ITransientExpression const*>( self ) );
os << Catch::Detail::stringify( expr.m_lhs );
}
public:
explicit constexpr UnaryExpr( LhsT lhs )
: ITransientExpression{ &streamFn, false, static_cast<bool>(lhs) },
m_lhs( lhs )
{}
};
template<typename LhsT>
class ExprLhs {
LhsT m_lhs;
public:
explicit constexpr ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}
// We rely on reference collapsing if LhsT is already a reference.
// Used in the out-of-class decomposition operators
constexpr LhsT& getLhs() { return m_lhs; }
constexpr auto makeUnaryExpr() const -> UnaryExpr<LhsT> {
return UnaryExpr<LhsT>{ m_lhs };
}
};
/*
* The comparison operators are **intentionally** not hidden friends.
*
* If they were hidden friends, all of them would get stamped out when
* a `ExprLhs` is instantiated with a new type. In practice, only few
* operators are used for each different `ExprLhs<T>`, so this avoids
* a lot of wasted work.
*/
#define CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( id, op ) \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT&& rhs ) \
->std::enable_if_t< \
Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \
Detail::negation<capture_by_value< \
Detail::RemoveCVRef_t<RhsT>>>>::value, \
BinaryExpr<LhsT, RhsT const&>> { \
return { static_cast<bool>( lhs.getLhs() op rhs ), \
lhs.getLhs(), \
#op##_sr, \
rhs }; \
} \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT rhs ) \
->std::enable_if_t< \
Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \
capture_by_value<RhsT>>::value, \
BinaryExpr<LhsT, RhsT>> { \
return { static_cast<bool>( lhs.getLhs() op rhs ), \
lhs.getLhs(), \
#op##_sr, \
rhs }; \
} \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT rhs ) \
->std::enable_if_t< \
Detail::conjunction< \
Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>, \
Detail::is_eq_0_comparable<LhsT>, /* We allow long because we \
want `ptr op NULL` to be \
accepted */ \
Detail::disjunction<std::is_same<RhsT, int>, \
std::is_same<RhsT, long>>>::value, \
BinaryExpr<LhsT, RhsT>> { \
if ( rhs != 0 ) { throw_test_failure_exception(); } \
return { static_cast<bool>( lhs.getLhs() op 0 ), \
lhs.getLhs(), \
#op##_sr, \
rhs }; \
} \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT rhs ) \
->std::enable_if_t< \
Detail::conjunction< \
Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>, \
Detail::is_eq_0_comparable<RhsT>, /* We allow long because we \
want `ptr op NULL` to be \
accepted */ \
Detail::disjunction<std::is_same<LhsT, int>, \
std::is_same<LhsT, long>>>::value, \
BinaryExpr<LhsT, RhsT>> { \
if ( lhs.getLhs() != 0 ) { throw_test_failure_exception(); } \
return { static_cast<bool>( 0 op rhs ), lhs.getLhs(), #op##_sr, rhs }; \
}
CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( eq, == )
CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( ne, != )
#undef CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR
#define CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( id, op ) \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT&& rhs ) \
->std::enable_if_t< \
Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \
Detail::negation<capture_by_value< \
Detail::RemoveCVRef_t<RhsT>>>>::value, \
BinaryExpr<LhsT, RhsT const&>> { \
return { static_cast<bool>( lhs.getLhs() op rhs ), \
lhs.getLhs(), \
#op##_sr, \
rhs }; \
} \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT rhs ) \
->std::enable_if_t< \
Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \
capture_by_value<RhsT>>::value, \
BinaryExpr<LhsT, RhsT>> { \
return { static_cast<bool>( lhs.getLhs() op rhs ), \
lhs.getLhs(), \
#op##_sr, \
rhs }; \
} \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT rhs ) \
->std::enable_if_t< \
Detail::conjunction< \
Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>, \
Detail::is_##id##_0_comparable<LhsT>, \
std::is_same<RhsT, int>>::value, \
BinaryExpr<LhsT, RhsT>> { \
if ( rhs != 0 ) { throw_test_failure_exception(); } \
return { static_cast<bool>( lhs.getLhs() op 0 ), \
lhs.getLhs(), \
#op##_sr, \
rhs }; \
} \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT rhs ) \
->std::enable_if_t< \
Detail::conjunction< \
Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>, \
Detail::is_##id##_0_comparable<RhsT>, \
std::is_same<LhsT, int>>::value, \
BinaryExpr<LhsT, RhsT>> { \
if ( lhs.getLhs() != 0 ) { throw_test_failure_exception(); } \
return { static_cast<bool>( 0 op rhs ), lhs.getLhs(), #op##_sr, rhs }; \
}
CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( lt, < )
CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( le, <= )
CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( gt, > )
CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( ge, >= )
#undef CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR
#define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( op ) \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT&& rhs ) \
->std::enable_if_t< \
!capture_by_value<Detail::RemoveCVRef_t<RhsT>>::value, \
BinaryExpr<LhsT, RhsT const&>> { \
return { static_cast<bool>( lhs.getLhs() op rhs ), \
lhs.getLhs(), \
#op##_sr, \
rhs }; \
} \
template <typename LhsT, typename RhsT> \
constexpr auto operator op( ExprLhs<LhsT>&& lhs, RhsT rhs ) \
->std::enable_if_t<capture_by_value<RhsT>::value, \
BinaryExpr<LhsT, RhsT>> { \
return { static_cast<bool>( lhs.getLhs() op rhs ), \
lhs.getLhs(), \
#op##_sr, \
rhs }; \
}
CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( | )
CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( & )
CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( ^)
#undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR
template <typename LhsT, typename RhsT>
auto operator&&( ExprLhs<LhsT>&&, RhsT&& )
-> BinaryExpr<LhsT, RhsT const&> {
static_assert(
always_false<RhsT>::value,
"operator&& is not supported inside assertions, "
"wrap the expression inside parentheses, or decompose it" );
}
template <typename LhsT, typename RhsT>
auto operator||( ExprLhs<LhsT>&&, RhsT&& )
-> BinaryExpr<LhsT, RhsT const&> {
static_assert(
always_false<RhsT>::value,
"operator|| is not supported inside assertions, "
"wrap the expression inside parentheses, or decompose it" );
}
struct Decomposer {
template <typename T,
std::enable_if_t<!capture_by_value<Detail::RemoveCVRef_t<T>>::value,
int> = 0>
constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs<T const&> {
return ExprLhs<const T&>{ lhs };
}
template <typename T,
std::enable_if_t<capture_by_value<T>::value, int> = 0>
constexpr friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs<T> {
return ExprLhs<T>{ value };
}
};
} // end namespace Catch
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#ifdef __clang__
# pragma clang diagnostic pop
#elif defined __GNUC__
# pragma GCC diagnostic pop
#endif
|