// 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 #define __CPP2_UTIL // If this implementation doesn't support source_location yet, disable it // TODO: technically this test should have included first, but GEFN #if !defined(_MSC_VER) && !defined(__cpp_lib_source_location) #undef CPP2_USE_SOURCE_LOCATION #endif // If the cppfront user requested -pure-cpp2, this will be set // and we should be using modules only #ifdef CPP2_USE_MODULES // If we have real modules, use those the best we can // as implementations are still underway #ifdef __cpp_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. As of this writing (September 2022) // no compiler will take this path yet, but they're on the way... 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!) import std.core; import std.regex; import std.filesystem; import std.memory; import std.threading; // Suppress spurious MSVC modules warning #pragma warning(disable:5050) #endif // Otherwise, "fake it till you make it"... include (nearly) 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 } // ... this should approximate "import std;" on those compilers #else #include #include #ifdef __cpp_lib_coroutine #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cpp_lib_source_location #include #endif #include #include #include #include #include #include #include #ifdef __cpp_lib_memory_resource #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() #include #endif #include #include #ifdef __cpp_lib_format #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cpp_lib_spanstream #include #endif #include #include #ifdef __cpp_lib_syncstream #include #endif #include #include #include #ifdef __cpp_lib_barrier #include #endif #include #include #ifdef __cpp_lib_latch #include #endif #include #ifdef __cpp_lib_semaphore #include #endif #include #ifdef __cpp_lib_jthread #include #endif #include #include // 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 #endif // Otherwise, we're not in -pure-cpp2 and so just #include // what we need in this header to make this self-contained #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CPP2_USE_SOURCE_LOCATION) #include #endif #endif #define CPP2_TYPEOF(x) std::remove_cvref_t #define CPP2_FORWARD(x) std::forward(x) 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 ; // Rarely, when really needed for speed optimization: Fastest type with at least N bits using i8_fast = std::int_fast8_t ; using i16_fast = std::int_fast16_t ; using i32_fast = std::int_fast32_t ; using i64_fast = std::int_fast64_t ; using u8_fast = std::uint_fast8_t ; using u16_fast = std::uint_fast16_t ; using u32_fast = std::uint_fast32_t ; using u64_fast = std::uint_fast64_t ; // Rarely, when really needed for space optimization: Smallest type with at least N bits using i8_small = std::int_least8_t ; using i16_small = std::int_least16_t ; using i32_small = std::int_least32_t ; using i64_small = std::int_least64_t ; using u8_small = std::uint_least8_t ; using u16_small = std::uint_least16_t; using u32_small = std::uint_least32_t; using u64_small = std::uint_least64_t; // Discouraged: Variable precision names // short using ushort = unsigned short; // int using ulong = unsigned long; // 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 //----------------------------------------------------------------------- // // 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* 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) -> handler; constexpr auto get_handler() const -> handler { return reporter; } constexpr auto expects (bool b, CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> void { if (!b) 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[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); } ); constexpr auto contract_group::set_handler(handler h) -> handler { Default.expects(h); auto old = reporter; reporter = h; return old; } // 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 Null.expects(p != CPP2_TYPEOF(p){}, "dynamic null dereference attempt detected" CPP2_SOURCE_LOCATION_ARG); return std::forward(p); } // Subscript bounds checking // auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) requires (std::is_integral_v && requires { std::ssize(x); x[arg]; }) { Bounds.expects(0 <= arg && arg < std::ssize(x), "out of bounds access attempt detected" CPP2_SOURCE_LOCATION_ARG); return std::forward(x) [ std::forward(arg) ]; } auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) requires (!(std::is_integral_v && requires { std::ssize(x); x[arg]; })) { return std::forward(x) [ std::forward(arg) ]; } //----------------------------------------------------------------------- // // 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 { return std::make_unique(std::forward(args)...); } } unique; [[maybe_unused]] struct { template [[nodiscard]] auto cpp2_new(auto&& ...args) const -> std::shared_ptr { return std::make_shared(std::forward(args)...); } } shared; template [[nodiscard]] auto cpp2_new(auto&& ...args) -> std::unique_ptr { return unique.cpp2_new(std::forward(args)...); } //----------------------------------------------------------------------- // // in For "in" parameter // //----------------------------------------------------------------------- // template using in = std::conditional_t < sizeof(T) < 2*sizeof(void*) && std::is_trivially_copy_constructible_v, T const, T const& >; //----------------------------------------------------------------------- // // Initialization: These are closely related... // // deferred_init For deferred-initialized local or member variable // // out For out parameter // //----------------------------------------------------------------------- // template class deferred_init { bool init = false; alignas(T) std::byte data[sizeof(T)]; // or: std::aligned_storage_t data 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.expects(init); return t(); } auto construct (auto&& ...args) -> void { Default.expects(!init); new (&data) T(std::forward(args)...); init = true; } auto construct_list(auto&& ...args) -> void { Default.expects(!init); new (&data) T{std::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 = std::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.expects( t); } out(deferred_init* dt) noexcept : dt{dt}, has_t{false} { Default.expects(dt); } out(out* ot) noexcept : ot{ot}, has_t{ot->has_t} { Default.expects(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 != std::uncaught_exceptions()) { Default.expects(!has_t); dt->destroy(); called_construct() = false; } } auto construct(auto&& ...args) -> void { if (has_t) { Default.expects( t ); *t = T(std::forward(args)...); } else { Default.expects( dt ); if (dt->init) { dt->value() = T(std::forward(args)...); } else { dt->construct(std::forward(args)...); called_construct() = true; } } } auto construct_list(auto&& ...args) -> void { if (has_t) { Default.expects( t ); *t = T{std::forward(args)...}; } else { Default.expects( dt ); if (dt->init) { dt->value() = T{std::forward(args)...}; } else { dt->construct_list(std::forward(args)...); called_construct() = true; } } } auto value() noexcept -> T& { if (has_t) { Default.expects( t ); return *t; } else { Default.expects( dt ); return dt->value(); } } }; //----------------------------------------------------------------------- // // CPP2_UFCS: Variadic macro generating a variadic lamba, oh my... // //----------------------------------------------------------------------- // #ifdef _MSC_VER #define CPP2_FORCE_INLINE [[msvc::forceinline]] #else #define CPP2_FORCE_INLINE __attribute__((always_inline)) #endif #define CPP2_UFCS(FUNCNAME,PARAM1,...) \ [](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE { \ if constexpr (requires{ std::forward(obj).FUNCNAME(std::forward(params)...); }) { \ return std::forward(obj).FUNCNAME(std::forward(params)...); \ } else { \ return FUNCNAME(std::forward(obj), std::forward(params)...); \ } \ }(PARAM1, __VA_ARGS__) #define CPP2_UFCS_0(FUNCNAME,PARAM1) \ [](auto&& obj) CPP2_FORCE_INLINE { \ if constexpr (requires{ std::forward(obj).FUNCNAME(); }) { \ return std::forward(obj).FUNCNAME(); \ } else { \ return FUNCNAME(std::forward(obj)); \ } \ }(PARAM1) #define CPP2_UFCS_REMPARENS(...) __VA_ARGS__ #define CPP2_UFCS_TEMPLATE(FUNCNAME,TEMPARGS,PARAM1,...) \ [](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE { \ if constexpr (requires{ std::forward(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward(params)...); }) { \ return std::forward(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward(params)...); \ } else { \ return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward(obj), std::forward(params)...); \ } \ }(PARAM1, __VA_ARGS__) #define CPP2_UFCS_TEMPLATE_0(FUNCNAME,TEMPARGS,PARAM1) \ [](auto&& obj) CPP2_FORCE_INLINE { \ if constexpr (requires{ std::forward(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \ return std::forward(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \ } else { \ return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward(obj)); \ } \ }(PARAM1) //-------------------------------------------------------------------- //----------------------------------------------------------------------- // // is and as // //----------------------------------------------------------------------- // //------------------------------------------------------------------------------------------------------------- // Built-in is // // For use when returning "no such thing", such as // when customizing is/as for std::variant struct nonesuch_ { auto operator==(auto const&) -> bool { return false; } }; static nonesuch_ nonesuch; // 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