FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

// 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

Back | FazBrowse Home | New Git URL