[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h [Back]  [Original]

//  Copyright 2022-2026 Herb Sutter
//  SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//  
//  Part of the Cppfront Project, under the Apache License v2.0 with LLVM Exceptions.
//  See https://github.com/hsutter/cppfront/blob/main/LICENSE for license information.


//===========================================================================
//  Cpp2 utilities:
//      Language support implementations
//      #include'd by generated Cpp1 code

//  There are two kinds of entities in this file.
//
//  1)  Entities in namespace cpp2:: itself, and documented at /cppfront/docs
//
//      These are intended for programs to use directly, to the extent
//      described in the documentation. Using any parts not described in the
//      documentation is not supported.
//
//  2)  Entities in namespace cpp2::impl::, and macros
//
//      These should not be used by the program. They form the language
//      support library intended to be called only from generated code.
// 
//      For example, if a Cpp2 function leaves a local variable
//      uninitialized, cppfront will generate uses of impl::deferred_init
//      under the covers and guarantee it is constructed exactly once, so
//      the implementation here doesn't need to check for double construction
//      because it can't happen; using the name impl::deferred_init directly
//      from program code is not supported.
// 
//  3)  Entities in other subnamespaces, such as cpp2::string_util
// 
//      These are typically metafunction "runtime-library" functions,
//      implementation details called by metafunction-generated code.
//      For example, @regex generates code that uses string_util:: functions.
// 
//===========================================================================

#ifndef CPP2_CPP2UTIL_H
#define CPP2_CPP2UTIL_H

//  If this implementation doesn't support source_location yet, disable it
#include 

#undef CPP2_USE_SOURCE_LOCATION
#if defined(__cpp_lib_source_location)
    #define CPP2_USE_SOURCE_LOCATION Yes
#endif

//  If the user requested making the entire C++ standard library available
//  via module import (incl. via -pure-cpp2) or header include, do that
#if defined(CPP2_IMPORT_STD) || defined(CPP2_INCLUDE_STD)

    //  If C++23 'import std;' was requested but isn't available, fall back
    //  to the 'include std' path
    #if defined(CPP2_IMPORT_STD) && defined(__cpp_lib_modules)
        import std.compat;
        #include 
    //  If 'include std' was requested, include all standard headers.
    //  This list tracks the current draft standard, so as of this
    //  writing includes draft C++26 headers like .
    //  Use a feature test #ifdef for each header that isn't supported
    //  by all of { VS 2022, g++-10, clang++-12 }
    #else
        #ifdef _MSC_VER
            #include "intrin.h"
        #endif
        #include 
        #include 
        #include 
        #include 
        #ifdef __cpp_lib_barrier
            #include 
        #endif
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #ifdef __cpp_lib_coroutine
            #include 
        #endif
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #if __has_include()
            #include 
        #endif
        #include 
        #include 
        #ifdef __cpp_lib_debugging
            #include 
        #endif
        #include 
        #ifndef CPP2_NO_EXCEPTIONS
            #include 
        #endif
        // libstdc++ currently has a dependency on linking TBB if  is
        // included, and TBB seems to be not automatically installed and linkable
        // on some GCC installations, so let's not pull in that little-used header
        // in our -pure-cpp2 "import std;" simulation mode... if you need this,
        // use mixed mode (not -pure-cpp2) and #include all the headers you need
        // including this one
        //
        // #include 
        #ifdef __cpp_lib_expected
            #include 
        #endif
        #include 
        #if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
            #include 
        #endif
        #ifdef __cpp_lib_flat_map
            #include 
        #endif
        #ifdef __cpp_lib_flat_set
            #include 
        #endif
        #include 
        #include 
        #include 
        #include 
        #ifdef __cpp_lib_generator
            #include 
        #endif
        #ifdef __cpp_lib_hazard_pointer
            #include 
        #endif
        #include 
        #ifdef __cpp_lib_inplace_vector
            #include 
        #endif
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #ifdef __cpp_lib_latch
            #include 
        #endif
        #include 
        #ifdef __cpp_lib_linalg
            #include 
        #endif
        #include 
        #include 
        #include 
        #ifdef __cpp_lib_mdspan
            #include 
        #endif
        #include 
        #ifdef __cpp_lib_memory_resource
            #include 
        #endif
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
        #ifdef __cpp_lib_print
            #include 
        #endif
        #include 
        #include 
        #include 
        #include 
        #ifdef __cpp_lib_rcu
            #include 
        #endif
        #include 
        #include 
        #ifdef __cpp_lib_semaphore
            #include 
        #endif
        #include 
        #include 
        #ifdef __cpp_lib_source_location
            #include 
        #endif
        #include 
        #ifdef __cpp_lib_spanstream
            #include 
        #endif
        #include 
        #include 
        #ifdef __cpp_lib_stacktrace
            #include 
        #endif
        #ifdef __cpp_lib_stdatomic_h
            #include 
        #endif
        #include 
        #if __has_include()
            #if !defined(_MSC_VER) || _HAS_CXX23
                #include 
            #endif
        #endif
        #ifdef __cpp_lib_jthread
            #include 
        #endif
        #include 
        #include 
        #include 
        #ifdef __cpp_lib_syncbuf
            #include 
        #endif
        #include 
        #ifdef __cpp_lib_text_encoding
            #include 
        #endif
        #include 
        #include 
        #include 
        #include 
        #ifndef CPP2_NO_RTTI
            #include 
        #endif
        #include 
        #include 
        #include 
        #include 
        #include 
        #include 
    #endif

//  Otherwise, just #include the facilities used in this header
#else
    #ifdef _MSC_VER
        #include "intrin.h"
    #endif
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #ifndef CPP2_NO_EXCEPTIONS
        #include 
    #endif
    #ifdef __cpp_lib_expected
        #include 
    #endif
    #if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
        #include 
    #endif
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #if defined(CPP2_USE_SOURCE_LOCATION)
        #include 
    #endif
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #ifndef CPP2_NO_RTTI
        #include 
    #endif
    #include 
    #include 
    #include 
#endif

// Required for pure Cpp2 tests to pass on MSVC
// #include  causes C2995 of math tempaltes
#ifndef EXIT_FAILURE
    #define EXIT_FAILURE 1
#endif

//  cpp2util.h uses signed integer types for indices and container sizes
//  so disable clang signed-to-unsigned conversion warnings in this header.
#ifdef __clang__
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wsign-conversion"
#endif

//-----------------------------------------------------------------------
//
//  Macros
//
//-----------------------------------------------------------------------
//
#define CPP2_TYPEOF(x)              std::remove_cvref_t
#if __cplusplus >= 202302L && \
    ( \
     (defined(__clang_major__) && __clang_major__ >= 15) \
     || (defined(__GNUC__) && __GNUC__ >= 12) \
     )
#define CPP2_COPY(x)                auto(x)
#else
#define CPP2_COPY(x)                CPP2_TYPEOF(x)(x)
#endif
#define CPP2_FORWARD(x)             std::forward(x)
#define CPP2_PACK_EMPTY(x)          (sizeof...(x) == 0)
#define CPP2_CONTINUE_BREAK(NAME)   goto CONTINUE_##NAME; CONTINUE_##NAME: continue; goto BREAK_##NAME; BREAK_##NAME: break;
                                    // these redundant goto's to avoid 'unused label' warnings

//  Compiler version identification.
// 
//  This can use useful with 'if constexpr' to disable code known not to
//  work on some otherwise-supported compilers (without macros), for example:
// 
//    //  Disable tests on lower-level compilers that have blocking bugs
//    [] () { if constexpr (V) {
//        // ... tests that would fail due to older compilers' bugs ...
//    }}();
//
//  Note: Test Clang first because it pretends to be other compilers.
//
#if defined(__clang_major__)
    constexpr auto gcc_ver   = 0;
    constexpr auto clang_ver = __clang_major__ * 100 + __clang_minor__;
    constexpr auto msvc_ver  = 0;
#elif defined(_MSC_VER)
    constexpr auto gcc_ver   = 0;
    constexpr auto clang_ver = 0;
    constexpr auto msvc_ver  = _MSC_VER;
#elif defined(__GNUC__)
    constexpr auto gcc_ver   = __GNUC__ * 100 + __GNUC_MINOR__;
    constexpr auto clang_ver = 0;
    constexpr auto msvc_ver  = 0;
#endif

constexpr auto gcc_clang_msvc_min_versions(
    auto gcc,
    auto clang,
    auto msvc
)
{
    return gcc_ver >= gcc || clang_ver >= clang || msvc_ver >= msvc;
}


#if defined(_MSC_VER) && !defined(__clang_major__)
   // MSVC can't handle 'inline constexpr' variables yet in all cases
    #define CPP2_CONSTEXPR const
#else
    #define CPP2_CONSTEXPR constexpr
#endif


// Workaround .
#define CPP2_FORCE_INLINE_LAMBDA_CLANG /* empty */

#if defined(_MSC_VER) && !defined(__clang_major__)
    #define CPP2_FORCE_INLINE              __forceinline
    #define CPP2_FORCE_INLINE_LAMBDA       [[msvc::forceinline]]
    #define CPP2_LAMBDA_NO_DISCARD
#else
    #define CPP2_FORCE_INLINE              __attribute__((always_inline))
    #if defined(__clang__)
        #define CPP2_FORCE_INLINE_LAMBDA       /* empty */
        #undef CPP2_FORCE_INLINE_LAMBDA_CLANG
        #define CPP2_FORCE_INLINE_LAMBDA_CLANG __attribute__((always_inline))
    #else
        #define CPP2_FORCE_INLINE_LAMBDA       __attribute__((always_inline))
    #endif

    #if defined(__clang_major__)
        //  Also check __cplusplus, only to satisfy Clang -pedantic-errors
        #if __cplusplus >= 202302L && (__clang_major__ > 13 || (__clang_major__ == 13 && __clang_minor__ >= 2))
            #define CPP2_LAMBDA_NO_DISCARD   [[nodiscard]]
        #else
            #define CPP2_LAMBDA_NO_DISCARD
        #endif
    #elif defined(__GNUC__)
        #if __GNUC__ >= 9
            #define CPP2_LAMBDA_NO_DISCARD   [[nodiscard]]
        #else
            #define CPP2_LAMBDA_NO_DISCARD
        #endif
        #if ((__GNUC__ * 100) + __GNUC_MINOR__) < 1003
            //  GCC 10.2 doesn't support this feature (10.3 is fine)
            #undef  CPP2_FORCE_INLINE_LAMBDA
            #define CPP2_FORCE_INLINE_LAMBDA
        #endif
    #else
        #define CPP2_LAMBDA_NO_DISCARD
    #endif
#endif


namespace cpp2 {

//-----------------------------------------------------------------------
//
//  Convenience names for fundamental types
//
//  Note: De jure, some of these are optional per the C and C++ standards
//        De facto, all of these are supported in all implementations I know of
//
//-----------------------------------------------------------------------
//

//  Encouraged by default: Fixed-precision names
using i8        = std::int8_t        ;
using i16       = std::int16_t       ;
using i32       = std::int32_t       ;
using i64       = std::int64_t       ;
using u8        = std::uint8_t       ;
using u16       = std::uint16_t      ;
using u32       = std::uint32_t      ;
using u64       = std::uint64_t      ;

//  Discouraged: Variable precision names
//                 short
using ushort     = unsigned short;
//                 int
using uint       = unsigned int;
//                 long
using ulong      = unsigned long;
using longlong   = long long;
using ulonglong  = unsigned long long;
using longdouble = long double;

//  Strongly discouraged, for compatibility/interop only
using _schar     = signed char;      // normally use i8 instead
using _uchar     = unsigned char;    // normally use u8 instead


//-----------------------------------------------------------------------
//
//  An implementation of GSL's narrow_cast with a clearly 'unchecked' name
//
//-----------------------------------------------------------------------
//
namespace impl {

template< typename To, typename From >
constexpr auto is_narrowing_v =
    // [dcl.init.list] 7.1
    (std::is_floating_point_v && std::is_integral_v) ||
    // [dcl.init.list] 7.2
    (std::is_floating_point_v && std::is_floating_point_v && sizeof(From) > sizeof(To)) || // NOLINT(misc-redundant-expression)
    // [dcl.init.list] 7.3
    (std::is_integral_v && std::is_floating_point_v) ||
    (std::is_enum_v && std::is_floating_point_v) ||
    // [dcl.init.list] 7.4
    (std::is_integral_v && std::is_integral_v && sizeof(From) > sizeof(To)) || // NOLINT(misc-redundant-expression)
    (std::is_enum_v && std::is_integral_v && sizeof(From) > sizeof(To)) ||
    // [dcl.init.list] 7.5
    (std::is_pointer_v && std::is_same_v)
    ;

}


template 
constexpr auto unchecked_narrow( X x ) noexcept 
    -> decltype(auto)
    requires (
        impl::is_narrowing_v
        || (
            std::is_arithmetic_v
            && std::is_arithmetic_v
            )
        )
{
    return static_cast(x);
}


template 
constexpr auto unchecked_cast( X&& x ) noexcept 
    -> decltype(auto)
{
    return static_cast(CPP2_FORWARD(x));
}


//-----------------------------------------------------------------------
//
//  contract_group
//
//-----------------------------------------------------------------------
//

#ifdef CPP2_USE_SOURCE_LOCATION
    #define CPP2_SOURCE_LOCATION_PARAM              , [[maybe_unused]] std::source_location where
    #define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT , [[maybe_unused]] std::source_location where = std::source_location::current()
    #define CPP2_SOURCE_LOCATION_PARAM_SOLO         [[maybe_unused]] std::source_location where
    #define CPP2_SOURCE_LOCATION_ARG                , where
    #define CPP2_SOURCE_LOCATION_VALUE              (cpp2::to_string(where.file_name()) + "(" + cpp2::to_string(where.line()) + ") " + where.function_name())
#else
    #define CPP2_SOURCE_LOCATION_PARAM
    #define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT
    #define CPP2_SOURCE_LOCATION_PARAM_SOLO
    #define CPP2_SOURCE_LOCATION_ARG
    #define CPP2_SOURCE_LOCATION_VALUE              std::string("")
#endif

//  For C++23: make this std::string_view and drop the macro
//      Before C++23 std::string_view was not guaranteed to be trivially copyable,
//      and so in will pass it by const& and really it should be by value
#define CPP2_MESSAGE_PARAM  char const*
#define CPP2_CONTRACT_MSG   cpp2::message_to_cstr_adapter

inline auto message_to_cstr_adapter( CPP2_MESSAGE_PARAM msg ) -> CPP2_MESSAGE_PARAM { return msg ? msg : ""; }
inline auto message_to_cstr_adapter( std::string const& msg ) -> CPP2_MESSAGE_PARAM { return msg.c_str(); }

class contract_group {
public:
    using handler = void (*)(CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM);

    constexpr contract_group  (handler h = {}) : reporter{h} { }
    constexpr auto set_handler(handler h = {}) { reporter = h; }
    constexpr auto is_active  () const -> bool    { return reporter != handler{}; }

    constexpr auto enforce(bool b, CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT)
                                          -> void { if (!b) report_violation(msg CPP2_SOURCE_LOCATION_ARG); }
    constexpr auto report_violation(CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT)
                                          -> void { if (reporter) reporter(msg CPP2_SOURCE_LOCATION_ARG); }
private:
    handler reporter;
};

[[noreturn]] inline auto report_and_terminate(std::string_view group, CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) noexcept -> void {
    std::cerr
#ifdef CPP2_USE_SOURCE_LOCATION
         boolean_testable;} && ...)) {
            ((CPP2_FORWARD(fun)(type_it{}) && (found = Is, true)) || ...);
        } 
    }(std::index_sequence_for());
    return found;
}

template 
constexpr auto type_find_if(C const&, F&& fun)
{
    return type_find_if(CPP2_FORWARD(fun));
}

template 
constexpr auto variant_contains_type(std::variant)
{
    if constexpr (is_any) {
        return std::true_type{};
    } else {
        return std::false_type{};
    }
}

template 
using constness_like_t =
  std::conditional_t<
    std::is_const_v<
      std::remove_pointer_t<
        std::remove_reference_t
      >
    >,
    std::add_const_t,
    std::remove_const_t
  >;

template
[[nodiscard]] constexpr auto forward_like(U&& x) noexcept -> decltype(auto)
{
    constexpr bool is_adding_const = std::is_const_v;
    if constexpr (std::is_lvalue_reference_v)
    {
        if constexpr (is_adding_const)
            return std::as_const(x);
        else
            return static_cast(x);
    }
    else
    {
        if constexpr (is_adding_const)
            return std::move(std::as_const(x));
        else
            return std::move(x);
    }
}


namespace impl {

template  struct dependent_false : std::false_type {};


//-----------------------------------------------------------------------
// 
//  Invalid/null dereference checking - cases that would result in UB.
//
//     - Null pointer
//     - std::unique_ptr that owns nothing
//     - std::shared_ptr with no managed object
//     - std::optional with no value
//     - std::expected containing an unexpected value
//
//  Note: For naming simplicity we consider all the above cases to be "null" states so that
//        we can write: `*assert_not_null(object)`.
//
template
concept UniquePtr = std::is_same_v;

template
concept SharedPtr = std::is_same_v;

template
concept Optional = std::is_same_v;

#ifdef __cpp_lib_expected

template
concept Expected = std::is_same_v;

#endif

constexpr auto assert_not_null(auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
{
    //  NOTE: This "!= T{}" test may or may not work for STL iterators. The standard
    //        doesn't guarantee that using == and != will reliably report whether an
    //        STL iterator has the default-constructed value. So use it only for raw *...
    if constexpr (std::is_pointer_v) {
        if (arg == CPP2_TYPEOF(arg){}) {
            null_safety.report_violation("dynamic null dereference attempt detected" CPP2_SOURCE_LOCATION_ARG);
        };
    }
    else if constexpr (UniquePtr) {
        if (!arg) {
            null_safety.report_violation("std::unique_ptr is empty" CPP2_SOURCE_LOCATION_ARG);
        }
    }
    else if constexpr (SharedPtr) {
        if (!arg) {
            null_safety.report_violation("std::shared_ptr is empty" CPP2_SOURCE_LOCATION_ARG);
        }
    }
    else if constexpr (Optional) {
        if (!arg.has_value()) {
            null_safety.report_violation("std::optional does not contain a value" CPP2_SOURCE_LOCATION_ARG);
        }
    }
#ifdef __cpp_lib_expected
    else if constexpr (Expected) {
        if (!arg.has_value()) {
            null_safety.report_violation("std::expected has an unexpected value" CPP2_SOURCE_LOCATION_ARG);
        }
    }
#endif

    return CPP2_FORWARD(arg);
}


//-----------------------------------------------------------------------
// 
//  Integer divide-by-zero checking - cases that would result in UB.
//  
//  Notes:
//      NumType is the Numerator type
//      arg is the denominator value
//      Both must be integral to enable the check
//

#define CPP2_ASSERT_NOT_ZERO_IMPL \
    requires (std::is_integral_v && \
              std::is_integral_v) \
{ \
    if (0 == arg) { \
        type_safety.report_violation("integer division by zero attempt detected"  CPP2_SOURCE_LOCATION_ARG); \
    } \
    return arg; \
}

template
constexpr auto assert_not_zero([[maybe_unused]] char _ CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> auto
    CPP2_ASSERT_NOT_ZERO_IMPL

template
constexpr auto assert_not_zero([[maybe_unused]] char _ CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> auto
{
    return arg;
}

template
constexpr auto assert_not_zero(auto   arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> auto
    CPP2_ASSERT_NOT_ZERO_IMPL

template
constexpr auto assert_not_zero(auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
    requires (!std::is_integral_v
              || !std::is_integral_v)
{
    return CPP2_FORWARD(arg);
}

#define CPP2_ASSERT_NOT_ZERO(NumType, arg)          (cpp2::impl::assert_not_zero((arg)))
#define CPP2_ASSERT_NOT_ZERO_LITERAL(NumType, arg)  (cpp2::impl::assert_not_zero('_'))


//-----------------------------------------------------------------------
// 
//  Subscript bounds checking - cases that would result in UB.
//
#define CPP2_ASSERT_IN_BOUNDS_IMPL \
    requires (std::is_integral_v && \
              requires { std::size(x); std::ssize(x); x[arg]; std::begin(x) + 2; }) \
{ \
    auto max = [&]() -> auto { \
        if constexpr (std::is_signed_v) { return std::ssize(x); } \
        else { return std::size(x); } \
    }; \
    auto msg = "out of bounds access attempt detected - attempted access at index " + std::to_string(arg) + ", "; \
    if (max() > 0 ) { \
        msg += "[min,max] range is [0," + std::to_string(max()-1) + "]"; \
    } \
    else { \
        msg += "but container is empty"; \
    } \
    if (!(0  decltype(auto)
    CPP2_ASSERT_IN_BOUNDS_IMPL

template
constexpr auto assert_in_bounds(auto&& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
{
    return CPP2_FORWARD(x) [ arg ];
}

constexpr auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
    CPP2_ASSERT_IN_BOUNDS_IMPL

    constexpr auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
{
    return CPP2_FORWARD(x) [ CPP2_FORWARD(arg) ];
}

#define CPP2_ASSERT_IN_BOUNDS(x,arg)         (cpp2::impl::assert_in_bounds((x),(arg)))
#define CPP2_ASSERT_IN_BOUNDS_LITERAL(x,arg) (cpp2::impl::assert_in_bounds(x))

#ifdef CPP2_NO_RTTI
// Compile-Time type name deduction for -fno-rtti builds
//
constexpr auto process_type_name(std::string_view name) -> std::string_view {
#if defined(__clang__) || defined(__GNUC__)
    constexpr auto type_prefix = std::string_view("T = ");
    constexpr auto types_close_parenthesis = ']';
#elif defined(_MSC_VER)
    constexpr auto type_prefix = std::string_view("type_name is unsafe and not allowed - are you missing parentheses?"
            );
    }
    else if constexpr (
        std::is_integral_v &&
        std::is_integral_v &&
        std::is_signed_v != std::is_signed_v
        )
    {
        //  Note: It's tempting here to "just call std::cmp_*() instead"
        //  which does signed/unsigned relational comparison correctly
        //  for negative values, and so silently "fix that for you." But
        //  doing that has security pitfalls for the reasons described at
        //  https://github.com/hsutter/cppfront/issues/220, so this
        //  static_assert to reject the comparison is the right way to go.
        static_assert(
            program_violates_type_safety_guarantee,
            "mixed signed/unsigned comparison is unsafe - prefer using .ssize() instead of .size(), consider using std::cmp_less or similar instead, or consider explicitly casting one of the values to change signedness by using 'as' or 'cpp2::unchecked_narrow'"
            );
    }
}


CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u) 
    -> decltype(auto)
    requires requires {CPP2_FORWARD(t) < CPP2_FORWARD(u);}
{
    cmp_mixed_signedness_check();
    return CPP2_FORWARD(t) < CPP2_FORWARD(u);
}

CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u) 
    -> decltype(auto)
{
    static_assert(
        program_violates_type_safety_guarantee,
        "attempted to compare '' for incompatible types"
        );
    return nonesuch;
}


CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u) 
    -> decltype(auto)
    requires requires {CPP2_FORWARD(t) >= CPP2_FORWARD(u);}
{
    cmp_mixed_signedness_check();
    return CPP2_FORWARD(t) >= CPP2_FORWARD(u);
}

CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u) 
    -> decltype(auto)
{
    static_assert(
        program_violates_type_safety_guarantee,
        "attempted to compare '>=' for incompatible types"
        );
    return nonesuch;
}


//-----------------------------------------------------------------------
//
//  A static-asserting "as" for better diagnostics than raw 'nonesuch'
//
//  Note for the future: This needs go after all 'as', which is fine for
//  the ones in this file but will have problems with further user-
//  defined 'as' customizations. One solution would be to make the main
//  'as' be a class template, and have all customizations be actual
//  specializations... that way name lookup should find the primary
//  template first and then see later specializations. Or we could just
//  remove this and live with the 'nonesuch' error messages. Either way,
//  we don't need anything more right now, this solution is fine to
//  unblock general progress
//
//-----------------------------------------------------------------------
//
template< typename C >
constexpr auto as_( auto&& x ) -> decltype(auto)
{
    if constexpr (is_narrowing_v) {
        static_assert(
            program_violates_type_safety_guarantee,
            "'as' does not allow unsafe possibly-lossy narrowing conversions - if you're sure you want this, use 'unchecked_narrow' to explicitly force the conversion and possibly lose information"
        );
    }
    else if constexpr (is_unsafe_pointer_conversion_v)
    {
        static_assert(
            program_violates_type_safety_guarantee,
            "'as' does not allow unsafe pointer conversions - if you're sure you want this, use `unchecked_cast()` to explicitly force the cast"
            );
    }
    else if constexpr( std::is_same_v< CPP2_TYPEOF(as(CPP2_FORWARD(x))), nonesuch_ > ) {
        static_assert(
            program_violates_type_safety_guarantee,
            "No safe 'as' cast available - please check your cast"
        );
    }
    //  else
    return as(CPP2_FORWARD(x));
}

template< typename C, auto x >
constexpr auto as_() -> decltype(auto)
{
    if constexpr (requires { as(); }) {
        if constexpr( std::is_same_v< CPP2_TYPEOF((as())), nonesuch_ > ) {
            static_assert(
                program_violates_type_safety_guarantee,
                "'as' does not allow unsafe possibly-lossy narrowing conversions - if you're sure you want this, use `unchecked_narrow()` to explicitly force the conversion and possibly lose information"
                );
        }
    }
    else {
        static_assert(
            program_violates_type_safety_guarantee,
            "No safe 'as' cast available - please check your cast"
        );
    }
    //  else
    return as();
}

} // impl


}

using cpp2::cpp2_new;


//  Stabilize line numbers for "compatibility" static assertions
//  and UFCS error output to keep regression test outputs cleaner
#line 9999

//-----------------------------------------------------------------------
//
//  CPP2_UFCS: Variadic macro generating a variadic lambda, oh my...
//
//-----------------------------------------------------------------------
//

#define CPP2_UFCS_EMPTY(...)
#define CPP2_UFCS_IDENTITY(...)  __VA_ARGS__
#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__

// Ideally, the expression `CPP2_UFCS_IS_NOTHROW` expands to
// is in the _noexcept-specifier_ of the UFCS lambda, but without 'std::declval'.
// To workaround [GCC bug 101043](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101043),
// we instead make it a template parameter of the UFCS lambda.
// But using a template parameter, Clang also ICEs on an application.
// So we use these `NOTHROW` macros to fall back to the ideal for when not using GCC.
#define CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,...) \
   requires { requires  requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); }; \
              requires    noexcept(std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...)); } \
|| requires { requires !requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); }; \
              requires noexcept(MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(std::declval(), std::declval()...)); }
#define CPP2_UFCS_IS_NOTHROW_PARAM(...)                     /*empty*/
#define CPP2_UFCS_IS_NOTHROW_ARG(MVFWD,QUALID,TEMPKW,...)   CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,__VA_ARGS__)
#if defined(__GNUC__) && !defined(__clang__)
    #undef  CPP2_UFCS_IS_NOTHROW_PARAM
    #undef  CPP2_UFCS_IS_NOTHROW_ARG
    #define CPP2_UFCS_IS_NOTHROW_PARAM(MVFWD,QUALID,TEMPKW,...) , bool IsNothrow = CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,__VA_ARGS__)
    #define CPP2_UFCS_IS_NOTHROW_ARG(...)                       IsNothrow
    #if __GNUC__ < 11
        #undef  CPP2_UFCS_IS_NOTHROW_PARAM
        #undef  CPP2_UFCS_IS_NOTHROW_ARG
        #define CPP2_UFCS_IS_NOTHROW_PARAM(...)    /*empty*/
        #define CPP2_UFCS_IS_NOTHROW_ARG(...)      false // GCC 10 UFCS is always potentially-throwing.
    #endif
#endif

// Ideally, the expression `CPP2_UFCS_CONSTRAINT_ARG` expands to
// is in the _requires-clause_ of the UFCS lambda.
// To workaround an MSVC bug within a member function 'F' where UFCS is also for 'F'
// (),
// we instead make it a template parameter of the UFCS lambda.
// But using a template parameter, Clang also ICEs and GCC rejects a local 'F'.
// Also, Clang rejects the SFINAE test case when using 'std::declval'.
// So we use these `CONSTRAINT` macros to fall back to the ideal for when not using MSVC.
#define CPP2_UFCS_CONSTRAINT_PARAM(...)                   /*empty*/
#define CPP2_UFCS_CONSTRAINT_ARG(MVFWD,QUALID,TEMPKW,...) \
   requires { CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); } \
|| requires { MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); }
#if defined(_MSC_VER)
    #undef  CPP2_UFCS_CONSTRAINT_PARAM
    #undef  CPP2_UFCS_CONSTRAINT_ARG
    #define CPP2_UFCS_CONSTRAINT_PARAM(MVFWD,QUALID,TEMPKW,...) , bool IsViable = \
   requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); } \
|| requires { MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(std::declval(), std::declval()...); }
    #define CPP2_UFCS_CONSTRAINT_ARG(...)                 IsViable
#endif

#define CPP2_UFCS_(LAMBDADEFCAPT,SFINAE,MVFWD,QUALID,TEMPKW,...) \
[LAMBDADEFCAPT]< \
    typename Obj, typename... Params \
    CPP2_UFCS_IS_NOTHROW_PARAM(MVFWD,QUALID,TEMPKW,__VA_ARGS__) \
    CPP2_UFCS_CONSTRAINT_PARAM(MVFWD,QUALID,TEMPKW,__VA_ARGS__) \
  > \
  CPP2_LAMBDA_NO_DISCARD (Obj&& obj, Params&& ...params) CPP2_FORCE_INLINE_LAMBDA_CLANG \
  noexcept(CPP2_UFCS_IS_NOTHROW_ARG(MVFWD,QUALID,TEMPKW,__VA_ARGS__)) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) \
    SFINAE( requires CPP2_UFCS_CONSTRAINT_ARG(MVFWD,QUALID,TEMPKW,__VA_ARGS__) ) \
  { \
    if constexpr      (requires{ CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); }) { \
        return                   CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
    } \
    else if constexpr (requires{ MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); }) { \
        return                   MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
    } \
    else if constexpr (requires{ obj.CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); }) { \
        static_assert( cpp2::impl::dependent_false::value, "error: implicit discard of an object's modified value is not allowed - this function call modifies 'obj', but 'obj' is never used again in the function so the new value is never used - if that's what you intended, add another line '_ = obj;' afterward to explicitly discard the new value of the object" ); \
        CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
        MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
    } \
    else if constexpr (requires{ MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(obj, CPP2_FORWARD(params)...); }) { \
        static_assert( cpp2::impl::dependent_false::value, "error: implicit discard of an object's modified value is not allowed - this function call modifies 'obj', but 'obj' is never used again in the function so the new value is never used - if that's what you intended, add another line '_ = obj;' afterward to explicitly discard the new value of the object" ); \
        CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
        MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
    } \
    else { \
        static_assert( cpp2::impl::dependent_false::value, "this function call syntax tries 'obj.func(...)', then 'func(obj,...);', but both failed - if this function call is passing a local variable that will be modified by the function, but that variable is never used again in the function so the new value is never used, that's likely the problem - if that's what you intended, add another line '_ = obj;' afterward to explicitly discard the new value of the object" ); \
        CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
        MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
    } \
  }

#define CPP2_UFCS(...)                                    CPP2_UFCS_(&,CPP2_UFCS_EMPTY,CPP2_UFCS_IDENTITY,(),,__VA_ARGS__)
#define CPP2_UFCS_MOVE(...)                               CPP2_UFCS_(&,CPP2_UFCS_EMPTY,std::move,(),,__VA_ARGS__)
#define CPP2_UFCS_FORWARD(...)                            CPP2_UFCS_(&,CPP2_UFCS_EMPTY,CPP2_FORWARD,(),,__VA_ARGS__)
#define CPP2_UFCS_TEMPLATE(...)                           CPP2_UFCS_(&,CPP2_UFCS_EMPTY,CPP2_UFCS_IDENTITY,(),template,__VA_ARGS__)
#define CPP2_UFCS_QUALIFIED_TEMPLATE(QUALID,...)          CPP2_UFCS_(&,CPP2_UFCS_EMPTY,CPP2_UFCS_IDENTITY,QUALID,template,__VA_ARGS__)
#define CPP2_UFCS_NONLOCAL(...)                           CPP2_UFCS_(,CPP2_UFCS_IDENTITY,CPP2_UFCS_IDENTITY,(),,__VA_ARGS__)
#define CPP2_UFCS_TEMPLATE_NONLOCAL(...)                  CPP2_UFCS_(,CPP2_UFCS_IDENTITY,CPP2_UFCS_IDENTITY,(),template,__VA_ARGS__)
#define CPP2_UFCS_QUALIFIED_TEMPLATE_NONLOCAL(QUALID,...) CPP2_UFCS_(,CPP2_UFCS_IDENTITY,CPP2_UFCS_IDENTITY,QUALID,template,__VA_ARGS__)


//  GCC 10 doesn't support 'requires' in forward declarations in some cases
//  Workaround: Disable the requires clause where that gets reasonable behavior
//  Diagnostic: static_assert the other cases that can't be worked around
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ == 10
    #define CPP2_REQUIRES(...) /* empty */
    #define CPP2_REQUIRES_(...) static_assert(false, "GCC 11 or higher is required to support variables and type-scope functions that have a 'requires' clause. This includes a type-scope 'forward' parameter of non-wildcard type, such as 'func: (this, forward s: std::string)', which relies on being able to add a 'requires' clause - in that case, use 'forward s: _' instead if you need the result to compile with GCC 10.")
#else
    #define CPP2_REQUIRES(...) requires (__VA_ARGS__)
    #define CPP2_REQUIRES_(...) requires (__VA_ARGS__)
#endif

//  Restore clang signed-to-unsigned conversion warnings
#ifdef __clang__
    #pragma clang diagnostic pop
#endif

#endif // CPP2_CPP2UTIL_H

Web Proxy Viewer  |  New URL  |  Original Page