From 263915832993dd12beee10e204f9ebcc6c786ed2 Mon Sep 17 00:00:00 2001 From: Amlal El Mahrouss Date: Sat, 30 Dec 2023 23:39:37 +0100 Subject: Meta: initial commit of WestCo optimized toolchain. Signed-off-by: Amlal El Mahrouss --- CompilerDriver/cc2/include/cpp2util.h | 1999 +++++++++++++++++++++++++++++++++ 1 file changed, 1999 insertions(+) create mode 100644 CompilerDriver/cc2/include/cpp2util.h (limited to 'CompilerDriver/cc2/include/cpp2util.h') diff --git a/CompilerDriver/cc2/include/cpp2util.h b/CompilerDriver/cc2/include/cpp2util.h new file mode 100644 index 0000000..463dd99 --- /dev/null +++ b/CompilerDriver/cc2/include/cpp2util.h @@ -0,0 +1,1999 @@ + +// 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 cppfront user requested making the entire C++ standard library +// available via module import or header include, do that +#if defined(CPP2_IMPORT_STD) || defined(CPP2_INCLUDE_STD) + + // If C++23 'import std;' was requested and is available, use that + #if defined(CPP2_IMPORT_STD) && defined(__cpp_lib_modules) + + #ifndef _MSC_VER + // This is the ideal -- note that we just voted "import std;" + // into draft C++23 in late July 2022, so implementers haven't + // had time to catch up yet + import std; + #else // MSVC + // Note: When C++23 "import std;" is available, we will switch to that here + // In the meantime, this is what works on MSVC which is the only compiler + // I've been able to get access to that implements modules enough to demo + // (but we'll have more full-C++20 compilers soon!) + #ifdef _MSC_VER + #include "intrin.h" + #endif + import std.core; + import std.filesystem; + import std.memory; + import std.regex; + import std.threading; + + // Suppress spurious MSVC modules warning + #pragma warning(disable:5050) + #endif + + // Otherwise, as a fallback if 'import std;' was requested, or else + // because 'include all std' was requested, include all the standard + // headers, with a feature test #ifdef for each header that + // isn't yet 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 + #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 + #include + #include + #include + #include + #include + #include + #include + #include + #ifdef __cpp_lib_latch + #include + #endif + #include + #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 + #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() + #include + #endif + #ifdef __cpp_lib_jthread + #include + #endif + #include + #include + #include + #ifdef __cpp_lib_syncstream + #include + #endif + #include + #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 + #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 +#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]; +}; + + +//----------------------------------------------------------------------- +// +// 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 + +auto message_to_cstr_adapter( CPP2_MESSAGE_PARAM msg ) -> CPP2_MESSAGE_PARAM { return msg ? msg : ""; } +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 + << where.file_name() << "(" + << where.line() << ") " + << where.function_name() << ": " +#endif + << group << " violation"; + if (msg && msg[0] != '\0') { + std::cerr << ": " << msg; + } + std::cerr << "\n"; + std::terminate(); +} + +auto inline Default = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Contract", msg CPP2_SOURCE_LOCATION_ARG); + } +); +auto inline Bounds = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Bounds safety", msg CPP2_SOURCE_LOCATION_ARG); + } +); +auto inline Null = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Null safety", msg CPP2_SOURCE_LOCATION_ARG); + } +); +auto inline Type = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Type safety", msg CPP2_SOURCE_LOCATION_ARG); + } +); +auto inline Testing = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Testing", msg CPP2_SOURCE_LOCATION_ARG); + } +); + + +// Null pointer deref checking +// +auto assert_not_null(auto&& p 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 (p == CPP2_TYPEOF(p){}) { + Null.report_violation("dynamic null dereference attempt detected" CPP2_SOURCE_LOCATION_ARG); + }; + } + return CPP2_FORWARD(p); +} + +// Subscript bounds checking +// +auto assert_in_bounds_impl(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> void + 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 <= arg && arg < max())) { + Bounds.report_violation(msg.c_str() CPP2_SOURCE_LOCATION_ARG); + } +} + +auto assert_in_bounds_impl(auto&&, auto&& CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> void +{ +} + +#define CPP2_ASSERT_IN_BOUNDS(x, arg) (cpp2::assert_in_bounds_impl((x),(arg)), (x)[(arg)]) + + +//----------------------------------------------------------------------- +// +// Support wrappers that unblock using this file in environments that +// disable EH or RTTI +// +// Note: This is not endorsing disabling those features, it's just +// recognizing that disabling them is popular (e.g., games, WASM) +// and so we should remove a potential adoption blocker... only a +// few features in this file depend on EH or RTTI anyway, and +// wouldn't be exercised in such an environment anyway so there +// is no real net loss here +// +//----------------------------------------------------------------------- +// + +[[noreturn]] auto Throw(auto&& x, [[maybe_unused]] char const* msg) -> void { +#ifdef CPP2_NO_EXCEPTIONS + auto err = std::string{"exceptions are disabled with -fno-exceptions - attempted to throw exception with type \"" + typeid(decltype(x)).name() + "\""}; + if (msg) { + err += " and the message \"" + msg + "\""; + } + Type.report_violation( err ); + std::terminate(); +#else + throw CPP2_FORWARD(x); +#endif +} + +inline auto Uncaught_exceptions() -> int { +#ifdef CPP2_NO_EXCEPTIONS + return 0; +#else + return std::uncaught_exceptions(); +#endif +} + +template +auto Dynamic_cast( [[maybe_unused]] auto&& x ) -> decltype(auto) { +#ifdef CPP2_NO_RTTI + Type.report_violation( "'as' dynamic casting is disabled with -fno-rtti" ); + return nullptr; +#else + return dynamic_cast(CPP2_FORWARD(x)); +#endif +} + +template +auto Typeid() -> decltype(auto) { +#ifdef CPP2_NO_RTTI + Type.report_violation( "'any' dynamic casting is disabled with -fno-rtti" ); +#else + return typeid(T); +#endif +} + +auto Typeid( [[maybe_unused]] auto&& x ) -> decltype(auto) { +#ifdef CPP2_NO_RTTI + Type.report_violation( "'typeid' is disabled with -fno-rtti" ); +#else + return typeid(CPP2_FORWARD(x)); +#endif +} + + +//----------------------------------------------------------------------- +// +// Arena objects for std::allocators +// +// Note: cppfront translates "new" to "cpp2_new", so in Cpp2 code +// these are invoked by simply "unique.new" etc. +// +//----------------------------------------------------------------------- +// +struct { + template + [[nodiscard]] auto cpp2_new(auto&& ...args) const -> std::unique_ptr { + // Prefer { } to ( ) so that initializing a vector with + // (10), (10, 20), and (10, 20, 30) is consistent + if constexpr (requires { T{CPP2_FORWARD(args)...}; }) { + // This is because apparently make_unique can't deal with list + // initialization of aggregates, even after P0960 + return std::unique_ptr( new T{CPP2_FORWARD(args)...} ); + } + else { + return std::make_unique(CPP2_FORWARD(args)...); + } + } +} inline unique; + +[[maybe_unused]] struct { + template + [[nodiscard]] auto cpp2_new(auto&& ...args) const -> 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) <= 2*sizeof(void*) + && std::is_trivially_copy_constructible_v; + +template + requires std::is_class_v || std::is_union_v || std::is_array_v || std::is_function_v +constexpr bool prefer_pass_by_value = false; + +template + requires (!std::is_void_v) +using in = + std::conditional_t < + prefer_pass_by_value, + T const, + T const& + >; + + +//----------------------------------------------------------------------- +// +// 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_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(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(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(std::declval(), std::declval()...)); } +#define CPP2_UFCS_IS_NOTHROW_PARAM(...) /*empty*/ +#define CPP2_UFCS_IS_NOTHROW_ARG(QUALID,TEMPKW,...) CPP2_UFCS_IS_NOTHROW(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(QUALID,TEMPKW,...) , bool IsNothrow = CPP2_UFCS_IS_NOTHROW(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(QUALID,TEMPKW,...) \ + requires { CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); } \ +|| requires { 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(QUALID,TEMPKW,...) , bool IsViable = \ + requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); } \ +|| requires { CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(std::declval(), std::declval()...); } + #define CPP2_UFCS_CONSTRAINT_ARG(...) IsViable +#endif + +#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \ +[LAMBDADEFCAPT]< \ + typename Obj, typename... Params \ + CPP2_UFCS_IS_NOTHROW_PARAM(QUALID,TEMPKW,__VA_ARGS__) \ + CPP2_UFCS_CONSTRAINT_PARAM(QUALID,TEMPKW,__VA_ARGS__) \ + > \ + CPP2_LAMBDA_NO_DISCARD (Obj&& obj, Params&& ...params) CPP2_FORCE_INLINE_LAMBDA_CLANG \ + noexcept(CPP2_UFCS_IS_NOTHROW_ARG(QUALID,TEMPKW,__VA_ARGS__)) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) \ + requires CPP2_UFCS_CONSTRAINT_ARG(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 CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \ + } \ +} + +#define CPP2_UFCS(...) CPP2_UFCS_(&,(),,__VA_ARGS__) +#define CPP2_UFCS_TEMPLATE(...) CPP2_UFCS_(&,(),template,__VA_ARGS__) +#define CPP2_UFCS_QUALIFIED_TEMPLATE(QUALID,...) CPP2_UFCS_(&,QUALID,template,__VA_ARGS__) +#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__) +#define CPP2_UFCS_TEMPLATE_NONLOCAL(...) CPP2_UFCS_(,(),template,__VA_ARGS__) +#define CPP2_UFCS_QUALIFIED_TEMPLATE_NONLOCAL(QUALID,...) CPP2_UFCS_(,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<0>(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