Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-17 08:53:27

0001 
0002 //              Copyright Catch2 Authors
0003 // Distributed under the Boost Software License, Version 1.0.
0004 //   (See accompanying file LICENSE.txt or copy at
0005 //        https://www.boost.org/LICENSE_1_0.txt)
0006 
0007 // SPDX-License-Identifier: BSL-1.0
0008 #ifndef CATCH_DECOMPOSER_HPP_INCLUDED
0009 #define CATCH_DECOMPOSER_HPP_INCLUDED
0010 
0011 #include <catch2/catch_tostring.hpp>
0012 #include <catch2/internal/catch_stringref.hpp>
0013 #include <catch2/internal/catch_compare_traits.hpp>
0014 #include <catch2/internal/catch_test_failure_exception.hpp>
0015 #include <catch2/internal/catch_logical_traits.hpp>
0016 #include <catch2/internal/catch_compiler_capabilities.hpp>
0017 
0018 #include <type_traits>
0019 #include <iosfwd>
0020 
0021 /** \file
0022  * Why does decomposing look the way it does:
0023  *
0024  * Conceptually, decomposing is simple. We change `REQUIRE( a == b )` into
0025  * `Decomposer{} <= a == b`, so that `Decomposer{} <= a` is evaluated first,
0026  * and our custom operator is used for `a == b`, because `a` is transformed
0027  * into `ExprLhs<T&>` and then into `BinaryExpr<T&, U&>`.
0028  *
0029  * In practice, decomposing ends up a mess, because we have to support
0030  * various fun things.
0031  *
0032  * 1) Types that are only comparable with literal 0, and they do this by
0033  *    comparing against a magic type with pointer constructor and deleted
0034  *    other constructors. Example: `REQUIRE((a <=> b) == 0)` in libstdc++
0035  *
0036  * 2) Types that are only comparable with literal 0, and they do this by
0037  *    comparing against a magic type with consteval integer constructor.
0038  *    Example: `REQUIRE((a <=> b) == 0)` in current MSVC STL.
0039  *
0040  * 3) Types that have no linkage, and so we cannot form a reference to
0041  *    them. Example: some implementations of traits.
0042  *
0043  * 4) Starting with C++20, when the compiler sees `a == b`, it also uses
0044  *    `b == a` when constructing the overload set. For us this means that
0045  *    when the compiler handles `ExprLhs<T> == b`, it also tries to resolve
0046  *    the overload set for `b == ExprLhs<T>`.
0047  *
0048  * To accomodate these use cases, decomposer ended up rather complex.
0049  *
0050  * 1) These types are handled by adding SFINAE overloads to our comparison
0051  *    operators, checking whether `T == U` are comparable with the given
0052  *    operator, and if not, whether T (or U) are comparable with literal 0.
0053  *    If yes, the overload compares T (or U) with 0 literal inline in the
0054  *    definition.
0055  *
0056  *    Note that for extra correctness, we check  that the other type is
0057  *    either an `int` (literal 0 is captured as `int` by templates), or
0058  *    a `long` (some platforms use 0L for `NULL` and we want to support
0059  *    that for pointer comparisons).
0060  *
0061  * 2) For these types, `is_foo_comparable<T, int>` is true, but letting
0062  *    them fall into the overload that actually does `T == int` causes
0063  *    compilation error. Handling them requires that the decomposition
0064  *    is `constexpr`, so that P2564R3 applies and the `consteval` from
0065  *    their accompanying magic type is propagated through the `constexpr`
0066  *    call stack.
0067  *
0068  *    However this is not enough to handle these types automatically,
0069  *    because our default is to capture types by reference, to avoid
0070  *    runtime copies. While these references cannot become dangling,
0071  *    they outlive the constexpr context and thus the default capture
0072  *    path cannot be actually constexpr.
0073  *
0074  *    The solution is to capture these types by value, by explicitly
0075  *    specializing `Catch::capture_by_value` for them. Catch2 provides
0076  *    specialization for `std::foo_ordering`s, but users can specialize
0077  *    the trait for their own types as well.
0078  *
0079  * 3) If a type has no linkage, we also cannot capture it by reference.
0080  *    The solution is once again to capture them by value. We handle
0081  *    the common cases by using `std::is_arithmetic` as the default
0082  *    for `Catch::capture_by_value`, but that is only a some-effort
0083  *    heuristic. But as with 2), users can specialize `capture_by_value`
0084  *    for their own types as needed.
0085  *
0086  * 4) To support C++20 and make the SFINAE on our decomposing operators
0087  *    work, the SFINAE has to happen in return type, rather than in
0088  *    a template type. This is due to our use of logical type traits
0089  *    (`conjunction`/`disjunction`/`negation`), that we use to workaround
0090  *    an issue in older (9-) versions of GCC. I still blame C++20 for
0091  *    this, because without the comparison order switching, the logical
0092  *    traits could still be used in template type.
0093  *
0094  * There are also other side concerns, e.g. supporting both `REQUIRE(a)`
0095  * and `REQUIRE(a == b)`, or making `REQUIRE_THAT(a, IsEqual(b))` slot
0096  * nicely into the same expression handling logic, but these are rather
0097  * straightforward and add only a bit of complexity (e.g. common base
0098  * class for decomposed expressions).
0099  */
0100 
0101 #ifdef _MSC_VER
0102 #pragma warning(push)
0103 #pragma warning(disable:4389) // '==' : signed/unsigned mismatch
0104 #pragma warning(disable:4018) // more "signed/unsigned mismatch"
0105 #pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)
0106 #pragma warning(disable:4180) // qualifier applied to function type has no meaning
0107 #pragma warning(disable:4800) // Forcing result to true or false
0108 #endif
0109 
0110 #ifdef __clang__
0111 #  pragma clang diagnostic push
0112 #  pragma clang diagnostic ignored "-Wsign-compare"
0113 #  pragma clang diagnostic ignored "-Wnon-virtual-dtor"
0114 #elif defined __GNUC__
0115 #  pragma GCC diagnostic push
0116 #  pragma GCC diagnostic ignored "-Wsign-compare"
0117 #  pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
0118 #endif
0119 
0120 #if defined(CATCH_CPP20_OR_GREATER) && __has_include(<compare>)
0121 #  include <compare>
0122 #    if defined( __cpp_lib_three_way_comparison ) && \
0123             __cpp_lib_three_way_comparison >= 201907L
0124 #      define CATCH_CONFIG_CPP20_COMPARE_OVERLOADS
0125 #    endif
0126 #endif
0127 
0128 namespace Catch {
0129 
0130     namespace Detail {
0131         // This was added in C++20, but we require only C++14 for now.
0132         template <typename T>
0133         using RemoveCVRef_t = std::remove_cv_t<std::remove_reference_t<T>>;
0134     }
0135 
0136     // Note: There is nothing that stops us from extending this,
0137     //       e.g. to `std::is_scalar`, but the more encompassing
0138     //       traits are usually also more expensive. For now we
0139     //       keep this as it used to be and it can be changed later.
0140     template <typename T>
0141     struct capture_by_value
0142         : std::integral_constant<bool, std::is_arithmetic<T>{}> {};
0143 
0144 #if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS )
0145     template <>
0146     struct capture_by_value<std::strong_ordering> : std::true_type {};
0147     template <>
0148     struct capture_by_value<std::weak_ordering> : std::true_type {};
0149     template <>
0150     struct capture_by_value<std::partial_ordering> : std::true_type {};
0151 #endif
0152 
0153     template <typename T>
0154     struct always_false : std::false_type {};
0155 
0156     class ITransientExpression {
0157         bool m_isBinaryExpression;
0158         bool m_result;
0159 
0160     protected:
0161         ~ITransientExpression() = default;
0162 
0163     public:
0164         constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }
0165         constexpr auto getResult() const -> bool { return m_result; }
0166         //! This function **has** to be overriden by the derived class.
0167         virtual void streamReconstructedExpression( std::ostream& os ) const;
0168 
0169         constexpr ITransientExpression( bool isBinaryExpression, bool result )
0170         :   m_isBinaryExpression( isBinaryExpression ),
0171             m_result( result )
0172         {}
0173 
0174         constexpr ITransientExpression( ITransientExpression const& ) = default;
0175         constexpr ITransientExpression& operator=( ITransientExpression const& ) = default;
0176 
0177         friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) {
0178             expr.streamReconstructedExpression(out);
0179             return out;
0180         }
0181     };
0182 
0183     void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );
0184 
0185     template<typename LhsT, typename RhsT>
0186     class BinaryExpr  : public ITransientExpression {
0187         LhsT m_lhs;
0188         StringRef m_op;
0189         RhsT m_rhs;
0190 
0191         void streamReconstructedExpression( std::ostream &os ) const override {
0192             formatReconstructedExpression
0193                     ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) );
0194         }
0195 
0196     public:
0197         constexpr BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs )
0198         :   ITransientExpression{ true, comparisonResult },
0199             m_lhs( lhs ),
0200             m_op( op ),
0201             m_rhs( rhs )
0202         {}
0203 
0204         template<typename T>
0205         auto operator && ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {
0206             static_assert(always_false<T>::value,
0207             "chained comparisons are not supported inside assertions, "
0208             "wrap the expression inside parentheses, or decompose it");
0209         }
0210 
0211         template<typename T>
0212         auto operator || ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {
0213             static_assert(always_false<T>::value,
0214             "chained comparisons are not supported inside assertions, "
0215             "wrap the expression inside parentheses, or decompose it");
0216         }
0217 
0218         template<typename T>
0219         auto operator == ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {
0220             static_assert(always_false<T>::value,
0221             "chained comparisons are not supported inside assertions, "
0222             "wrap the expression inside parentheses, or decompose it");
0223         }
0224 
0225         template<typename T>
0226         auto operator != ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {
0227             static_assert(always_false<T>::value,
0228             "chained comparisons are not supported inside assertions, "
0229             "wrap the expression inside parentheses, or decompose it");
0230         }
0231 
0232         template<typename T>
0233         auto operator > ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {
0234             static_assert(always_false<T>::value,
0235             "chained comparisons are not supported inside assertions, "
0236             "wrap the expression inside parentheses, or decompose it");
0237         }
0238 
0239         template<typename T>
0240         auto operator < ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {
0241             static_assert(always_false<T>::value,
0242             "chained comparisons are not supported inside assertions, "
0243             "wrap the expression inside parentheses, or decompose it");
0244         }
0245 
0246         template<typename T>
0247         auto operator >= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {
0248             static_assert(always_false<T>::value,
0249             "chained comparisons are not supported inside assertions, "
0250             "wrap the expression inside parentheses, or decompose it");
0251         }
0252 
0253         template<typename T>
0254         auto operator <= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {
0255             static_assert(always_false<T>::value,
0256             "chained comparisons are not supported inside assertions, "
0257             "wrap the expression inside parentheses, or decompose it");
0258         }
0259     };
0260 
0261     template<typename LhsT>
0262     class UnaryExpr : public ITransientExpression {
0263         LhsT m_lhs;
0264 
0265         void streamReconstructedExpression( std::ostream &os ) const override {
0266             os << Catch::Detail::stringify( m_lhs );
0267         }
0268 
0269     public:
0270         explicit constexpr UnaryExpr( LhsT lhs )
0271         :   ITransientExpression{ false, static_cast<bool>(lhs) },
0272             m_lhs( lhs )
0273         {}
0274     };
0275 
0276 
0277     template<typename LhsT>
0278     class ExprLhs {
0279         LhsT m_lhs;
0280     public:
0281         explicit constexpr ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}
0282 
0283 #define CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( id, op )           \
0284     template <typename RhsT>                                                   \
0285     constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs )             \
0286         -> std::enable_if_t<                                                   \
0287             Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>,      \
0288                                 Detail::negation<capture_by_value<             \
0289                                     Detail::RemoveCVRef_t<RhsT>>>>::value,     \
0290             BinaryExpr<LhsT, RhsT const&>> {                                   \
0291         return {                                                               \
0292             static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
0293     }                                                                          \
0294     template <typename RhsT>                                                   \
0295     constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \
0296         -> std::enable_if_t<                                                   \
0297             Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>,      \
0298                                 capture_by_value<RhsT>>::value,                \
0299             BinaryExpr<LhsT, RhsT>> {                                          \
0300         return {                                                               \
0301             static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
0302     }                                                                          \
0303     template <typename RhsT>                                                   \
0304     constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \
0305         -> std::enable_if_t<                                                   \
0306             Detail::conjunction<                                               \
0307                 Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>,    \
0308                 Detail::is_eq_0_comparable<LhsT>,                              \
0309               /* We allow long because we want `ptr op NULL` to be accepted */ \
0310                 Detail::disjunction<std::is_same<RhsT, int>,                   \
0311                                     std::is_same<RhsT, long>>>::value,         \
0312             BinaryExpr<LhsT, RhsT>> {                                          \
0313         if ( rhs != 0 ) { throw_test_failure_exception(); }                    \
0314         return {                                                               \
0315             static_cast<bool>( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs };   \
0316     }                                                                          \
0317     template <typename RhsT>                                                   \
0318     constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \
0319         -> std::enable_if_t<                                                   \
0320             Detail::conjunction<                                               \
0321                 Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>,    \
0322                 Detail::is_eq_0_comparable<RhsT>,                              \
0323               /* We allow long because we want `ptr op NULL` to be accepted */ \
0324                 Detail::disjunction<std::is_same<LhsT, int>,                   \
0325                                     std::is_same<LhsT, long>>>::value,         \
0326             BinaryExpr<LhsT, RhsT>> {                                          \
0327         if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); }              \
0328         return { static_cast<bool>( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs };    \
0329     }
0330 
0331         CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( eq, == )
0332         CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( ne, != )
0333 
0334     #undef CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR
0335 
0336 
0337 #define CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( id, op )         \
0338     template <typename RhsT>                                                   \
0339     constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs )             \
0340         -> std::enable_if_t<                                                   \
0341             Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>,      \
0342                                 Detail::negation<capture_by_value<             \
0343                                     Detail::RemoveCVRef_t<RhsT>>>>::value,     \
0344             BinaryExpr<LhsT, RhsT const&>> {                                   \
0345         return {                                                               \
0346             static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
0347     }                                                                          \
0348     template <typename RhsT>                                                   \
0349     constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \
0350         -> std::enable_if_t<                                                   \
0351             Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>,      \
0352                                 capture_by_value<RhsT>>::value,                \
0353             BinaryExpr<LhsT, RhsT>> {                                          \
0354         return {                                                               \
0355             static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
0356     }                                                                          \
0357     template <typename RhsT>                                                   \
0358     constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \
0359         -> std::enable_if_t<                                                   \
0360             Detail::conjunction<                                               \
0361                 Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>,    \
0362                 Detail::is_##id##_0_comparable<LhsT>,                          \
0363                 std::is_same<RhsT, int>>::value,                               \
0364             BinaryExpr<LhsT, RhsT>> {                                          \
0365         if ( rhs != 0 ) { throw_test_failure_exception(); }                    \
0366         return {                                                               \
0367             static_cast<bool>( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs };   \
0368     }                                                                          \
0369     template <typename RhsT>                                                   \
0370     constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \
0371         -> std::enable_if_t<                                                   \
0372             Detail::conjunction<                                               \
0373                 Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>,    \
0374                 Detail::is_##id##_0_comparable<RhsT>,                          \
0375                 std::is_same<LhsT, int>>::value,                               \
0376             BinaryExpr<LhsT, RhsT>> {                                          \
0377         if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); }              \
0378         return { static_cast<bool>( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs };    \
0379     }
0380 
0381         CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( lt, < )
0382         CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( le, <= )
0383         CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( gt, > )
0384         CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( ge, >= )
0385 
0386     #undef CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR
0387 
0388 
0389 #define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( op )                        \
0390     template <typename RhsT>                                                   \
0391     constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs )             \
0392         -> std::enable_if_t<                                                   \
0393             !capture_by_value<Detail::RemoveCVRef_t<RhsT>>::value,             \
0394             BinaryExpr<LhsT, RhsT const&>> {                                   \
0395         return {                                                               \
0396             static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
0397     }                                                                          \
0398     template <typename RhsT>                                                   \
0399     constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \
0400         -> std::enable_if_t<capture_by_value<RhsT>::value,                     \
0401                             BinaryExpr<LhsT, RhsT>> {                          \
0402         return {                                                               \
0403             static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
0404     }
0405 
0406         CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|)
0407         CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&)
0408         CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^)
0409 
0410     #undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR
0411 
0412         template<typename RhsT>
0413         friend auto operator && ( ExprLhs &&, RhsT && ) -> BinaryExpr<LhsT, RhsT const&> {
0414             static_assert(always_false<RhsT>::value,
0415             "operator&& is not supported inside assertions, "
0416             "wrap the expression inside parentheses, or decompose it");
0417         }
0418 
0419         template<typename RhsT>
0420         friend auto operator || ( ExprLhs &&, RhsT && ) -> BinaryExpr<LhsT, RhsT const&> {
0421             static_assert(always_false<RhsT>::value,
0422             "operator|| is not supported inside assertions, "
0423             "wrap the expression inside parentheses, or decompose it");
0424         }
0425 
0426         constexpr auto makeUnaryExpr() const -> UnaryExpr<LhsT> {
0427             return UnaryExpr<LhsT>{ m_lhs };
0428         }
0429     };
0430 
0431     struct Decomposer {
0432         template <typename T,
0433                   std::enable_if_t<!capture_by_value<Detail::RemoveCVRef_t<T>>::value,
0434                       int> = 0>
0435         constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs<T const&> {
0436             return ExprLhs<const T&>{ lhs };
0437         }
0438 
0439         template <typename T,
0440                   std::enable_if_t<capture_by_value<T>::value, int> = 0>
0441         constexpr friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs<T> {
0442             return ExprLhs<T>{ value };
0443         }
0444     };
0445 
0446 } // end namespace Catch
0447 
0448 #ifdef _MSC_VER
0449 #pragma warning(pop)
0450 #endif
0451 #ifdef __clang__
0452 #  pragma clang diagnostic pop
0453 #elif defined __GNUC__
0454 #  pragma GCC diagnostic pop
0455 #endif
0456 
0457 #endif // CATCH_DECOMPOSER_HPP_INCLUDED