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

GitHub Viewer

// Copyright (c) Herb Sutter // SPDX-License-Identifier: CC-BY-NC-ND-4.0 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. //=========================================================================== // Cpp2 utilities: // Language support implementations // #include'd by generated Cpp1 code //=========================================================================== #ifndef CPP2_UTIL_H #define CPP2_UTIL_H // If this implementation doesn't support source_location yet, disable it #include #if !defined(_MSC_VER) && !defined(__cpp_lib_source_location) #undef CPP2_USE_SOURCE_LOCATION #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; // 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 #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_syncstream #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 #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 #if defined(CPP2_USE_SOURCE_LOCATION) #include #endif #include #include #include #include #include #include #ifndef CPP2_NO_RTTI #include #endif #include #include #include #endif #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 #if defined(_MSC_VER) // MSVC can't handle 'inline constexpr' yet in all cases #define CPP2_CONSTEXPR const #else #define CPP2_CONSTEXPR constexpr #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 //----------------------------------------------------------------------- // // General helpers // //----------------------------------------------------------------------- // inline constexpr auto max(auto... values) { return std::max( { values... } ); } template inline constexpr auto is_any = std::disjunction_v; template struct aligned_storage { alignas(Align) unsigned char data[Len]; }; template requires requires { *std::declval(); } using deref_t = decltype(*std::declval()); //----------------------------------------------------------------------- // // String: A helper workaround for passing a string literal as a // template argument // //----------------------------------------------------------------------- // template struct String { constexpr String(const char (&str)[N]) { std::copy_n(str, N, value); } auto operator(String const&) const = default; char value[N] = {}; }; //----------------------------------------------------------------------- // // contract_group // //----------------------------------------------------------------------- // #ifdef CPP2_USE_SOURCE_LOCATION #define CPP2_SOURCE_LOCATION_PARAM , std::source_location where #define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT , std::source_location where = std::source_location::current() #define CPP2_SOURCE_LOCATION_PARAM_SOLO std::source_location where #define CPP2_SOURCE_LOCATION_ARG , where #else #define CPP2_SOURCE_LOCATION_PARAM #define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT #define CPP2_SOURCE_LOCATION_PARAM_SOLO #define CPP2_SOURCE_LOCATION_ARG #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 get_handler() const -> handler { return reporter; } constexpr auto has_handler() 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 std::shared_ptr { // Prefer { } to ( ) as noted for unique.new // // Note this does mean we don't get the make_shared optimization a lot // of the time -- we can restore that as soon as make_shared improves to // allow list initialization. But the make_shared optimization isn't a // huge deal anyway: it saves one allocation, but most of the cost of // shared_ptrs is copying them and the allocation cost saving is probably // outweighed by just a couple of shared_ptr copies; also, the make_shared // optimization has the potential downside of keeping the raw storage // alive longer when there are weak_ptrs. So, yes, we can and should // restore the make_shared optimization as soon as make_shared supports // list init, but I don't think it's all that important AFAIK if constexpr (requires { T{CPP2_FORWARD(args)...}; }) { // Why this calls 'unique.new': The workaround to use { } initialization // requires calling naked 'new' to allocate the object separately anyway, // so reuse the unique.new path that already does that (less code // duplication, plus encapsulate the naked 'new' in one place) return unique.cpp2_new(CPP2_FORWARD(args)...); } else { return std::make_shared(CPP2_FORWARD(args)...); } } } inline shared; template [[nodiscard]] auto cpp2_new(auto&& ...args) -> std::unique_ptr { return unique.cpp2_new(CPP2_FORWARD(args)...); } //----------------------------------------------------------------------- // // in For "in" parameter // //----------------------------------------------------------------------- // template constexpr bool prefer_pass_by_value = sizeof(T) ; //----------------------------------------------------------------------- // // Initialization: These are closely related... // // deferred_init For deferred-initialized local object // // out For out parameter // //----------------------------------------------------------------------- // template class deferred_init { alignas(T) std::byte data[sizeof(T)]; bool init = false; auto t() -> T& { return *std::launder(reinterpret_cast(&data)); } template friend class out; auto destroy() -> void { if (init) { t().~T(); } init = false; } public: deferred_init() noexcept { } ~deferred_init() noexcept { destroy(); } auto value() noexcept -> T& { Default.enforce(init); return t(); } auto construct(auto&& ...args) -> void { Default.enforce(!init); new (&data) T{CPP2_FORWARD(args)...}; init = true; } }; template class out { // Not going to bother with std::variant here union { T* t; deferred_init* dt; }; out* ot = {}; bool has_t; // Each out in a chain contains its own uncaught_count ... int uncaught_count = Uncaught_exceptions(); // ... but all in a chain share the topmost called_construct_ bool called_construct_ = false; public: out(T* t_) noexcept : t{ t_}, has_t{true} { Default.enforce( t); } out(deferred_init* dt_) noexcept : dt{dt_}, has_t{false} { Default.enforce(dt); } out(out* ot_) noexcept : ot{ot_}, has_t{ot_->has_t} { Default.enforce(ot); if (has_t) { t = ot->t; } else { dt = ot->dt; } } auto called_construct() -> bool& { if (ot) { return ot->called_construct(); } else { return called_construct_; } } // In the case of an exception, if the parameter was uninitialized // then leave it in the same state on exit (strong guarantee) ~out() { if (called_construct() && uncaught_count != Uncaught_exceptions()) { Default.enforce(!has_t); dt->destroy(); called_construct() = false; } } auto construct(auto&& ...args) -> void { if (has_t || called_construct()) { if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) { Default.enforce( t ); *t = T(CPP2_FORWARD(args)...); } else { Default.report_violation("attempted to copy assign, but copy assignment is not available"); } } else { Default.enforce( dt ); if (dt->init) { if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) { dt->value() = T(CPP2_FORWARD(args)...); } else { Default.report_violation("attempted to copy assign, but copy assignment is not available"); } } else { dt->construct(CPP2_FORWARD(args)...); called_construct() = true; } } } auto value() noexcept -> T& { if (has_t) { Default.enforce( t ); return *t; } else { Default.enforce( dt ); return dt->value(); } } }; //----------------------------------------------------------------------- // // CPP2_UFCS: Variadic macro generating a variadic lamba, oh my... // //----------------------------------------------------------------------- // // 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 #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,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) \ 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 { \ return MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \ } \ } #define CPP2_UFCS(...) CPP2_UFCS_(&,CPP2_UFCS_IDENTITY,(),,__VA_ARGS__) #define CPP2_UFCS_MOVE(...) CPP2_UFCS_(&,std::move,(),,__VA_ARGS__) #define CPP2_UFCS_FORWARD(...) CPP2_UFCS_(&,CPP2_FORWARD,(),,__VA_ARGS__) #define CPP2_UFCS_TEMPLATE(...) CPP2_UFCS_(&,CPP2_UFCS_IDENTITY,(),template,__VA_ARGS__) #define CPP2_UFCS_QUALIFIED_TEMPLATE(QUALID,...) CPP2_UFCS_(&,CPP2_UFCS_IDENTITY,QUALID,template,__VA_ARGS__) #define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,CPP2_UFCS_IDENTITY,(),,__VA_ARGS__) #define CPP2_UFCS_TEMPLATE_NONLOCAL(...) CPP2_UFCS_(,CPP2_UFCS_IDENTITY,(),template,__VA_ARGS__) #define CPP2_UFCS_QUALIFIED_TEMPLATE_NONLOCAL(QUALID,...) CPP2_UFCS_(,CPP2_UFCS_IDENTITY,QUALID,template,__VA_ARGS__) //----------------------------------------------------------------------- // // to_string for string interpolation // //----------------------------------------------------------------------- // // For use when returning "no such thing", such as // when customizing "as" for std::variant struct nonesuch_ { auto operator==(auto const&) -> bool { return false; } }; constexpr inline nonesuch_ nonesuch; inline auto to_string(...) -> std::string { return "(customize me - no cpp2::to_string overload exists for this type)"; } inline auto to_string(nonesuch_) -> std::string { return "(invalid type)"; } inline auto to_string(std::same_as auto const&) -> std::string { return "std::any"; } inline auto to_string(bool b) -> std::string { return b ? "true" : "false"; } template inline auto to_string(T const& t) -> std::string requires requires { std::to_string(t); } { return std::to_string(t); } inline auto to_string(char const& t) -> std::string { return std::string{t}; } inline auto to_string(char const* s) -> std::string { return std::string{s}; } inline auto to_string(std::string const& s) -> std::string const& { return s; } template inline auto to_string(T const& sv) -> std::string requires (std::is_convertible_v && !std::is_convertible_v) { return std::string{sv}; } template inline auto to_string(std::variant const& v) -> std::string; template < typename T, typename U> inline auto to_string(std::pair const& p) -> std::string; template < typename... Ts> inline auto to_string(std::tuple const& t) -> std::string; template inline auto to_string(std::optional const& o) -> std::string { if (o.has_value()) { return cpp2::to_string(o.value()); } return "(empty)"; } template inline auto to_string(std::variant const& v) -> std::string { if (v.valueless_by_exception()) return "(empty)"; // Need to guard this with is_any otherwise the get_if is illegal if constexpr (is_any) if (std::get_if(&v) != nullptr) return "(empty)"; return std::visit([](auto&& arg) -> std::string { return cpp2::to_string(arg); }, v); } template < typename T, typename U> inline auto to_string(std::pair const& p) -> std::string { return "(" + cpp2::to_string(p.first) + ", " + cpp2::to_string(p.second) + ")"; } template < typename... Ts> inline auto to_string(std::tuple const& t) -> std::string { if constexpr (sizeof...(Ts) == 0) { return "()"; } else { std::string out = "(" + cpp2::to_string(std::get(t)); std::apply([&out](auto&&, auto&&... args) { ((out += ", " + cpp2::to_string(args)), ...); }, t); out += ")"; return out; } } // MSVC supports it but doesn't define __cpp_lib_format until the ABI stablizes, but here // don't care about that, so consider it as supported since VS 2019 16.10 (_MSC_VER 1929) #if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929) inline auto to_string(auto&& value, std::string_view fmt) -> std::string { return std::vformat(fmt, std::make_format_args(CPP2_FORWARD(value))); } #else inline auto to_string(auto&& value, std::string_view) -> std::string { // This Cpp1 implementation does not support -ted string interpolation // so the best we can do is ignore the formatting request (degraded operation // seems better than a dynamic error message string or a hard error) return to_string(CPP2_FORWARD(value)); } #endif //----------------------------------------------------------------------- // // is and as // //----------------------------------------------------------------------- // //------------------------------------------------------------------------------------------------------------- // Built-in is // // For designating "holds no value" -- used only with is, not as // TODO: Does this really warrant a new synonym? Perhaps "is void" is enough using empty = void; // Templates // template constexpr auto is(C< Ts...> const& ) -> bool { return true; } #if defined(_MSC_VER) template constexpr auto is( T const& ) -> bool { return false; } #else template constexpr auto is( T const& ) -> bool { return false; } #endif template constexpr auto is( C const& ) -> bool { return true; } template constexpr auto is( T const& ) -> bool { return false; } // Types // template< typename C, typename X > auto is( X const& x ) -> bool { if constexpr ( std::is_same_v || std::is_base_of_v ) { return true; } else if constexpr ( std::is_base_of_v || ( std::is_polymorphic_v && std::is_polymorphic_v ) ) { if constexpr (std::is_pointer_v) { return Dynamic_cast(x) != nullptr; } else { return Dynamic_cast(&x) != nullptr; } } else if constexpr ( requires { *x; X(); } && std::is_same_v ) { return x == X(); } else { return false; } } // Values // inline constexpr auto is( auto const& x, auto&& value ) -> bool { // Value with customized operator_is case if constexpr (requires{ x.op_is(value); }) { return x.op_is(value); } // Predicate case else if constexpr (requires{ bool{ value(x) }; }) { return value(x); } else if constexpr (std::is_function_v || requires{ &value.operator(); }) { return false; } // Value equality case else if constexpr (requires{ bool{x == value}; }) { return x == value; } return false; } //------------------------------------------------------------------------------------------------------------- // Built-in as // // The 'as' cast functions are so use that order here // If it's confusing, we can switch this to template< typename To, typename From > inline 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)) || // [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)) || (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 inline constexpr auto program_violates_type_safety_guarantee = sizeof...(Ts) < 0; // For literals we can check for safe 'narrowing' at a compile time (e.g., 1 as std::size_t) template< typename C, auto x > inline constexpr bool is_castable_v = std::is_integral_v && std::is_integral_v && !(static_cast(static_cast(x)) != x || ( (std::is_signed_v != std::is_signed_v) && ((static_cast(x) < C{}) != (x < CPP2_TYPEOF(x){})) ) ); // As // template< typename C, auto x > requires (std::is_arithmetic_v && std::is_arithmetic_v) inline constexpr auto as() -> auto { if constexpr ( is_castable_v ) { return static_cast(x); } else { return nonesuch; } } template< typename C, typename X > auto as(X const& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) { if constexpr ( std::is_floating_point_v && std::is_floating_point_v && sizeof(CPP2_TYPEOF(x)) > sizeof(C) ) { return CPP2_COPY(nonesuch); } // Signed/unsigned conversions to a not-smaller type are handled as a precondition, // and trying to cast from a value that is in the half of the value space that isn't // representable in the target type C is flagged as a Type safety contract violation else if constexpr ( std::is_integral_v && std::is_integral_v && std::is_signed_v != std::is_signed_v && sizeof(CPP2_TYPEOF(x)) not lossy static_cast(c) == x && (c < C{}) == (x < CPP2_TYPEOF(x){}), "dynamic lossy narrowing conversion attempt detected" CPP2_SOURCE_LOCATION_ARG ); return CPP2_COPY(c); } else if constexpr (std::is_same_v && std::is_integral_v) { return cpp2::to_string(x); } else if constexpr (std::is_same_v) { return x; } else if constexpr (std::is_base_of_v) { return static_cast(x); } else if constexpr (std::is_base_of_v) { return Dynamic_cast(x); } else if constexpr ( std::is_pointer_v && std::is_pointer_v && requires { requires std::is_base_of_v; } ) { return Dynamic_cast(x); } else if constexpr (requires { C{x}; }) { // Experiment: Recognize the nested `::value_type` pattern for some dynamic library types // like std::optional, and try to prevent accidental narrowing conversions even when // those types themselves don't defend against them if constexpr( requires { requires std::is_convertible_v; } ) { if constexpr( is_narrowing_v) { return nonesuch; } } return C{x}; } else { return nonesuch; } } template< typename C, typename X > auto as( X& x ) -> decltype(auto) { if constexpr (std::is_same_v) { return x; } else if constexpr (std::is_base_of_v) { return static_cast(x); } else if constexpr (std::is_base_of_v) { return Dynamic_cast(x); } else { return as(std::as_const(x)); } } //------------------------------------------------------------------------------------------------------------- // std::variant is and as // // Common internal helper // template constexpr auto operator_as( std::variant && x ) -> decltype(auto) { if constexpr (I < std::variant_size_v) { return std::get( x ); } else { return nonesuch; } } template constexpr auto operator_as( std::variant & x ) -> decltype(auto) { if constexpr (I < std::variant_size_v) { return std::get( x ); } else { return nonesuch; } } template constexpr auto operator_as( std::variant const& x ) -> decltype(auto) { if constexpr (I < std::variant_size_v) { return std::get( x ); } else { return nonesuch; } } // is Type // template constexpr auto operator_is( std::variant const& x ) { return x.index(); } template auto is( std::variant const& x ); // is Value // template constexpr auto is( std::variant const& x, auto&& value ) -> bool { // Predicate case if constexpr (requires{ bool{ value(operator_as< 0>(x)) }; }) { if (x.index() == 0) return value(operator_as< 0>(x)); } else if constexpr (requires{ bool{ value(operator_as< 1>(x)) }; }) { if (x.index() == 1) return value(operator_as< 1>(x)); } else if constexpr (requires{ bool{ value(operator_as< 2>(x)) }; }) { if (x.index() == 2) return value(operator_as< 2>(x)); } else if constexpr (requires{ bool{ value(operator_as< 3>(x)) }; }) { if (x.index() == 3) return value(operator_as< 3>(x)); } else if constexpr (requires{ bool{ value(operator_as< 4>(x)) }; }) { if (x.index() == 4) return value(operator_as< 4>(x)); } else if constexpr (requires{ bool{ value(operator_as< 5>(x)) }; }) { if (x.index() == 5) return value(operator_as< 5>(x)); } else if constexpr (requires{ bool{ value(operator_as< 6>(x)) }; }) { if (x.index() == 6) return value(operator_as< 6>(x)); } else if constexpr (requires{ bool{ value(operator_as< 7>(x)) }; }) { if (x.index() == 7) return value(operator_as< 7>(x)); } else if constexpr (requires{ bool{ value(operator_as< 8>(x)) }; }) { if (x.index() == 8) return value(operator_as< 8>(x)); } else if constexpr (requires{ bool{ value(operator_as< 9>(x)) }; }) { if (x.index() == 9) return value(operator_as< 9>(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 10) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 11) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 12) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 13) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 14) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 15) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 16) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 17) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 18) return value(operator_as(x)); } else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 19) return value(operator_as(x)); } else if constexpr (std::is_function_v || requires{ &value.operator(); }) { return false; } // Value case else { if constexpr (requires{ bool{ operator_as< 0>(x) == value }; }) { if (x.index() == 0) return operator_as< 0>(x) == value; } if constexpr (requires{ bool{ operator_as< 1>(x) == value }; }) { if (x.index() == 1) return operator_as< 1>(x) == value; } if constexpr (requires{ bool{ operator_as< 2>(x) == value }; }) { if (x.index() == 2) return operator_as< 2>(x) == value; } if constexpr (requires{ bool{ operator_as< 3>(x) == value }; }) { if (x.index() == 3) return operator_as< 3>(x) == value; } if constexpr (requires{ bool{ operator_as< 4>(x) == value }; }) { if (x.index() == 4) return operator_as< 4>(x) == value; } if constexpr (requires{ bool{ operator_as< 5>(x) == value }; }) { if (x.index() == 5) return operator_as< 5>(x) == value; } if constexpr (requires{ bool{ operator_as< 6>(x) == value }; }) { if (x.index() == 6) return operator_as< 6>(x) == value; } if constexpr (requires{ bool{ operator_as< 7>(x) == value }; }) { if (x.index() == 7) return operator_as< 7>(x) == value; } if constexpr (requires{ bool{ operator_as< 8>(x) == value }; }) { if (x.index() == 8) return operator_as< 8>(x) == value; } if constexpr (requires{ bool{ operator_as< 9>(x) == value }; }) { if (x.index() == 9) return operator_as< 9>(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 10) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 11) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 12) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 13) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 14) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 15) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 16) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 17) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 18) return operator_as(x) == value; } if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 19) return operator_as(x) == value; } } return false; } // as // template auto is( std::variant const& x ) { if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 0>(x)), T >) { if (x.index() == 0) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 1>(x)), T >) { if (x.index() == 1) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 2>(x)), T >) { if (x.index() == 2) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 4>(x)), T >) { if (x.index() == 4) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 5>(x)), T >) { if (x.index() == 5) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 6>(x)), T >) { if (x.index() == 6) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 7>(x)), T >) { if (x.index() == 7) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 8>(x)), T >) { if (x.index() == 8) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 9>(x)), T >) { if (x.index() == 9) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 10) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 11) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 12) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 13) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 14) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 15) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 16) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 17) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 18) return true; } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 19) return true; } if constexpr (std::is_same_v< T, empty > ) { if (x.valueless_by_exception()) return true; // Need to guard this with is_any otherwise the get_if is illegal if constexpr (is_any) return std::get_if(&x) != nullptr; } return false; } template auto as( std::variant && x ) -> decltype(auto) { if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 0>(x)), T >) { if (x.index() == 0) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 1>(x)), T >) { if (x.index() == 1) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 2>(x)), T >) { if (x.index() == 2) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 4>(x)), T >) { if (x.index() == 4) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 5>(x)), T >) { if (x.index() == 5) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 6>(x)), T >) { if (x.index() == 6) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 7>(x)), T >) { if (x.index() == 7) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 8>(x)), T >) { if (x.index() == 8) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 9>(x)), T >) { if (x.index() == 9) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 10) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 11) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 12) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 13) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 14) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 15) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 16) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 17) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 18) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 19) return operator_as(x); } Throw( std::bad_variant_access(), "'as' cast failed for 'variant'"); } template auto as( std::variant & x ) -> decltype(auto) { if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 0>(x)), T >) { if (x.index() == 0) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 1>(x)), T >) { if (x.index() == 1) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 2>(x)), T >) { if (x.index() == 2) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 4>(x)), T >) { if (x.index() == 4) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 5>(x)), T >) { if (x.index() == 5) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 6>(x)), T >) { if (x.index() == 6) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 7>(x)), T >) { if (x.index() == 7) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 8>(x)), T >) { if (x.index() == 8) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 9>(x)), T >) { if (x.index() == 9) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 10) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 11) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 12) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 13) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 14) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 15) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 16) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 17) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 18) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 19) return operator_as(x); } Throw( std::bad_variant_access(), "'as' cast failed for 'variant'"); } template auto as( std::variant const& x ) -> decltype(auto) { if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 0>(x)), T >) { if (x.index() == 0) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 1>(x)), T >) { if (x.index() == 1) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 2>(x)), T >) { if (x.index() == 2) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 4>(x)), T >) { if (x.index() == 4) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 5>(x)), T >) { if (x.index() == 5) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 6>(x)), T >) { if (x.index() == 6) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 7>(x)), T >) { if (x.index() == 7) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 8>(x)), T >) { if (x.index() == 8) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 9>(x)), T >) { if (x.index() == 9) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 10) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 11) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 12) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 13) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 14) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 15) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 16) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 17) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 18) return operator_as(x); } if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 19) return operator_as(x); } Throw( std::bad_variant_access(), "'as' cast failed for 'variant'"); } //------------------------------------------------------------------------------------------------------------- // std::any is and as // // is Type // template requires (std::is_same_v && !std::is_same_v && !std::is_same_v) constexpr auto is( X const& x ) -> bool { return x.type() == Typeid(); } template requires (std::is_same_v && std::is_same_v) constexpr auto is( X const& x ) -> bool { return !x.has_value(); } // is Value // inline constexpr auto is( std::any const& x, auto&& value ) -> bool { // Predicate case if constexpr (requires{ bool{ value(x) }; }) { return value(x); } else if constexpr (std::is_function_v || requires{ &value.operator(); }) { return false; } // Value case else if constexpr (requires{ bool{ *std::any_cast(&x) == value }; }) { auto pvalue = std::any_cast(&x); return pvalue && *pvalue == value; } // else return false; } // as // template requires (!std::is_reference_v && std::is_same_v && !std::is_same_v) constexpr auto as( X const& x ) -> T { return std::any_cast( x ); } //------------------------------------------------------------------------------------------------------------- // std::optional is and as // // is Type // template requires std::is_same_v constexpr auto is( X const& x ) -> bool { return x.has_value(); } template requires std::is_same_v constexpr auto is( std::optional const& x ) -> bool { return !x.has_value(); } // is Value // template constexpr auto is( std::optional const& x, auto&& value ) -> bool { // Predicate case if constexpr (requires{ bool{ value(x) }; }) { return value(x); } else if constexpr (std::is_function_v || requires{ &value.operator(); }) { return false; } // Value case else if constexpr (requires{ bool{ x.value() == value }; }) { return x.has_value() && x.value() == value; } return false; } // as // template requires std::is_same_v constexpr auto as( X const& x ) -> decltype(auto) { return x.value(); } //----------------------------------------------------------------------- // // A variation of GSL's final_action_success / finally // // finally ensures something is run at the end of a scope always // // finally_success ensures something is run at the end of a scope // if no exception is thrown // // finally_presuccess ensures a group of add'd operations are run // immediately before (not after) the return if no exception is // thrown - right now this is used only for postconditions, so // they can inspect named return values before they're moved from // //----------------------------------------------------------------------- // template class finally_success { public: explicit finally_success(const F& ff) noexcept : f{ff} { } explicit finally_success(F&& ff) noexcept : f{std::move(ff)} { } ~finally_success() noexcept { if (invoke && ecount == std::uncaught_exceptions()) { f(); } } finally_success(finally_success&& that) noexcept : f(std::move(that.f)), invoke(std::exchange(that.invoke, false)) { } finally_success(finally_success const&) = delete; void operator= (finally_success const&) = delete; void operator= (finally_success&&) = delete; private: F f; int ecount = std::uncaught_exceptions(); bool invoke = true; }; template class finally { public: explicit finally(const F& ff) noexcept : f{ff} { } explicit finally(F&& ff) noexcept : f{std::move(ff)} { } ~finally() noexcept { f(); } finally(finally&& that) noexcept : f(std::move(that.f)), invoke(std::exchange(that.invoke, false)) { } finally (finally const&) = delete; void operator=(finally const&) = delete; void operator=(finally&&) = delete; private: F f; bool invoke = true; }; class finally_presuccess { public: finally_presuccess() = default; auto add(const auto& f) { fs.push_back(f); } // In compiled Cpp2 code, this function will be called // immediately before 'return' (both explicit and implicit) auto run() { if (invoke && ecount == std::uncaught_exceptions()) { for (auto const& f : fs) { f(); } } invoke = false; } ~finally_presuccess() noexcept { run(); } finally_presuccess(finally_presuccess const&) = delete; void operator= (finally_presuccess const&) = delete; void operator= (finally_presuccess &&) = delete; private: std::vector fs; int ecount = std::uncaught_exceptions(); bool invoke = true; }; //----------------------------------------------------------------------- // // args: see main() arguments as vector // //----------------------------------------------------------------------- // struct args_t : std::vector { args_t(int c, char** v) : vector{static_cast(c)}, argc{c}, argv{v} {} mutable int argc = 0; // mutable for compatibility with frameworks that take 'int& argc' char** argv = nullptr; }; inline auto make_args(int argc, char** argv) -> args_t { auto ret = args_t{argc, argv}; auto args = std::span(argv, static_cast(argc)); std::copy( args.begin(), args.end(), ret.data()); return ret; } //----------------------------------------------------------------------- // // alien_memory: memory typed as T but that is outside C++ and that the // compiler may not assume it knows anything at all about // //----------------------------------------------------------------------- // template using alien_memory = T volatile; //----------------------------------------------------------------------- // // An implementation of GSL's narrow_cast with a clearly 'unsafe' name // //----------------------------------------------------------------------- // template constexpr auto unsafe_narrow( X&& x ) noexcept -> decltype(auto) { return static_cast(CPP2_FORWARD(x)); } //----------------------------------------------------------------------- // // has_flags: query whether a flag_enum value has all flags in 'flags' set // // flags set of flags to check // // Returns a function object that takes a 'value' of the same type as // 'flags', and evaluates to true if and only if 'value' has set all of // the bits set in 'flags' // //----------------------------------------------------------------------- // template auto has_flags(T flags) { return [=](T value) { return (value & flags) == flags; }; } //----------------------------------------------------------------------- // // Speculative: RAII wrapping for the C standard library // // As part of embracing compatibility while also reducing what we have to // teach and learn about C++ (which includes the C standard library), I // was curious to see if we can improve use of the C standard library // from Cpp2 code... UFCS is a part of that, and then RAII destructors is // another that goes hand in hand with that, hence this section... // but see caveat note at the end. // //----------------------------------------------------------------------- // template class c_raii { T t; D dtor; public: c_raii( T t_, D d ) : t{ t_ } , dtor{ d } { } ~c_raii() { dtor(t); } operator T&() { return t; } c_raii(c_raii const&) = delete; auto operator=(c_raii const&) = delete; }; inline auto fopen( const char* filename, const char* mode ) { // Suppress annoying deprecation warning about fopen #ifdef _MSC_VER #pragma warning( push ) #pragma warning( disable : 4996 ) #endif auto x = std::fopen(filename, mode); #ifdef _MSC_VER #pragma warning( pop ) #endif if (!x) { Throw( std::make_error_condition(std::errc::no_such_file_or_directory), "'fopen' attempt failed"); } return c_raii( x, &std::fclose ); } // Caveat: There's little else in the C stdlib that allocates a resource... // // malloc is already wrapped like this via std::unique_ptr, which // typically uses malloc or gets memory from the same pool // thrd_create std::jthread is better // // ... is that it? I don't think it's useful to provide a c_raii just for fopen, // but perhaps c_raii may be useful for bringing forward third-party C code too, // with cpp2::fopen as a starting example. //----------------------------------------------------------------------- // // Signed/unsigned comparison checks // //----------------------------------------------------------------------- // template CPP2_FORCE_INLINE constexpr auto cmp_mixed_signedness_check() -> void { if constexpr ( std::is_same_v || std::is_same_v ) { static_assert( program_violates_type_safety_guarantee, "comparing bool values using < = > 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 instead, or consider explicitly casting one of the values to change signedness by using 'as' or 'cpp2::unsafe_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 > inline constexpr auto as_( auto&& x ) -> decltype(auto) { if constexpr (is_narrowing_v) { static_assert( program_violates_type_safety_guarantee, "'as' does not allow unsafe narrowing conversions - if you're sure you want this, use `unsafe_narrow()` to force the conversion" ); } 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 > inline 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, "Literal cannot be narrowed using 'as' - if you're sure you want this, use 'unsafe_narrow()' to force the conversion" ); } } else { static_assert( program_violates_type_safety_guarantee, "No safe 'as' cast available - please check your cast" ); } // else return as(); } } using cpp2::cpp2_new; // Stabilize line numbers for "compatibility" static assertions that we know // will fire for some compilers, to keep regression test outputs cleaner #line 9999 // 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 #endif

Back | FazBrowse Home | New Git URL