// Copyright 2022-2024 Herb Sutter // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Part of the Cppfront Project, under the Apache License v2.0 with LLVM Exceptions. // See https://github.com/hsutter/cppfront/blob/main/LICENSE for license information. //=========================================================================== // Cpp2 utilities: // Language support implementations // #include'd by generated Cpp1 code // There are two kinds of entities in this file. // // 1) Entities in namespace cpp2:: itself, and documented at /cppfront/docs // // These are intended for programs to use directly, to the extent // described in the documentation. Using any parts not described in the // documentation is not supported. // // 2) Entities in namespace cpp2::impl::, and macros // // These should not be used by the program. They form the language // support library intended to be called only from generated code. // // For example, if a Cpp2 function leaves a local variable // uninitialized, cppfront will generate uses of impl::deferred_init<> // under the covers and guarantee it is constructed exactly once, so // the implementation here doesn't need to check for double construction // because it can't happen; using the name impl::deferred_init directly // from program code is not supported. // // 3) Entities in other subnamespaces, such as cpp2::string_util // // These are typically metafunction "runtime-library" functions, // implementation details called by metafunction-generated code. // For example, @regex generates code that uses string_util:: functions. // //=========================================================================== #ifndef CPP2_CPP2UTIL_H #define CPP2_CPP2UTIL_H // If this implementation doesn't support source_location yet, disable it #include #undef CPP2_USE_SOURCE_LOCATION #if defined(__cpp_lib_source_location) #define CPP2_USE_SOURCE_LOCATION Yes #endif // If the user requested making the entire C++ standard library available // via module import (incl. via -pure-cpp2) or header include, do that #if defined(CPP2_IMPORT_STD) || defined(CPP2_INCLUDE_STD) // If C++23 'import std;' was requested but isn't available, fall back // to the 'include std' path #if defined(CPP2_IMPORT_STD) && defined(__cpp_lib_modules) import std.compat; // If 'include std' was requested, include all standard headers. // This list tracks the current draft standard, so as of this // writing includes draft C++26 headers like . // Use a feature test #ifdef for each header that isn't supported // by all of { VS 2022, g++-10, clang++-12 } #else #ifdef _MSC_VER #include "intrin.h" #endif #include #include #include #include #ifdef __cpp_lib_barrier #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cpp_lib_coroutine #include #endif #include #include #include #include #include #include #include #include #include #if __has_include() #include #endif #include #include #ifdef __cpp_lib_debugging #include #endif #include #ifndef CPP2_NO_EXCEPTIONS #include #endif // libstdc++ currently has a dependency on linking TBB if is // included, and TBB seems to be not automatically installed and linkable // on some GCC installations, so let's not pull in that little-used header // in our -pure-cpp2 "import std;" simulation mode... if you need this, // use mixed mode (not -pure-cpp2) and #include all the headers you need // including this one // // #include #ifdef __cpp_lib_expected #include #endif #include #if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929) #include #endif #ifdef __cpp_lib_flat_map #include #endif #ifdef __cpp_lib_flat_set #include #endif #include #include #include #include #ifdef __cpp_lib_generator #include #endif #ifdef __cpp_lib_hazard_pointer #include #endif #include #ifdef __cpp_lib_inplace_vector #include #endif #include #include #include #include #include #include #include #ifdef __cpp_lib_latch #include #endif #include #ifdef __cpp_lib_linalg #include #endif #include #include #include #ifdef __cpp_lib_mdspan #include #endif #include #ifdef __cpp_lib_memory_resource #include #endif #include #include #include #include #include #include #ifdef __cpp_lib_print #include #endif #include #include #include #include #ifdef __cpp_lib_rcu #include #endif #include #include #ifdef __cpp_lib_semaphore #include #endif #include #include #ifdef __cpp_lib_source_location #include #endif #include #ifdef __cpp_lib_spanstream #include #endif #include #include #ifdef __cpp_lib_stacktrace #include #endif #ifdef __cpp_lib_stdatomic_h #include #endif #include #if __has_include() #if !defined(_MSC_VER) || _HAS_CXX23 #include #endif #endif #ifdef __cpp_lib_jthread #include #endif #include #include #include #ifdef __cpp_lib_syncbuf #include #endif #include #ifdef __cpp_lib_text_encoding #include #endif #include #include #include #include #ifndef CPP2_NO_RTTI #include #endif #include #include #include #include #include #include #endif // Otherwise, just #include the facilities used in this header #else #ifdef _MSC_VER #include "intrin.h" #endif #include #include #include #include #include #include #include #ifndef CPP2_NO_EXCEPTIONS #include #endif #ifdef __cpp_lib_expected #include #endif #if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929) #include #endif #include #include #include #include #include #include #include #include #include #include #include #if defined(CPP2_USE_SOURCE_LOCATION) #include #endif #include #include #include #include #include #include #include #include #include #ifndef CPP2_NO_RTTI #include #endif #include #include #include #endif // cpp2util.h uses signed integer types for indices and container sizes // so disable clang signed-to-unsigned conversion warnings in this header. #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wsign-conversion" #endif //----------------------------------------------------------------------- // // Macros // //----------------------------------------------------------------------- // #define CPP2_TYPEOF(x) std::remove_cvref_t #if __cplusplus >= 202302L && \ ( \ (defined(__clang_major__) && __clang_major__ >= 15) \ || (defined(__GNUC__) && __GNUC__ >= 12) \ ) #define CPP2_COPY(x) auto(x) #else #define CPP2_COPY(x) CPP2_TYPEOF(x)(x) #endif #define CPP2_FORWARD(x) std::forward(x) #define CPP2_PACK_EMPTY(x) (sizeof...(x) == 0) #define CPP2_CONTINUE_BREAK(NAME) goto CONTINUE_##NAME; CONTINUE_##NAME: continue; goto BREAK_##NAME; BREAK_##NAME: break; // these redundant goto's to avoid 'unused label' warnings // Compiler version identification. // // This can use useful with 'if constexpr' to disable code known not to // work on some otherwise-supported compilers (without macros), for example: // // // Disable tests on lower-level compilers that have blocking bugs // [] () { if constexpr (V) { // // ... tests that would fail due to older compilers' bugs ... // }}(); // // Note: Test Clang first because it pretends to be other compilers. // #if defined(__clang_major__) constexpr auto gcc_ver = 0; constexpr auto clang_ver = __clang_major__ * 100 + __clang_minor__; constexpr auto msvc_ver = 0; #elif defined(_MSC_VER) constexpr auto gcc_ver = 0; constexpr auto clang_ver = 0; constexpr auto msvc_ver = _MSC_VER; #elif defined(__GNUC__) constexpr auto gcc_ver = __GNUC__ * 100 + __GNUC_MINOR__; constexpr auto clang_ver = 0; constexpr auto msvc_ver = 0; #endif constexpr auto gcc_clang_msvc_min_versions( auto gcc, auto clang, auto msvc ) { return gcc_ver >= gcc || clang_ver >= clang || msvc_ver >= msvc; } #if defined(_MSC_VER) && !defined(__clang_major__) // MSVC can't handle 'inline constexpr' variables yet in all cases #define CPP2_CONSTEXPR const #else #define CPP2_CONSTEXPR constexpr #endif // Workaround . #define CPP2_FORCE_INLINE_LAMBDA_CLANG /* empty */ #if defined(_MSC_VER) && !defined(__clang_major__) #define CPP2_FORCE_INLINE __forceinline #define CPP2_FORCE_INLINE_LAMBDA [[msvc::forceinline]] #define CPP2_LAMBDA_NO_DISCARD #else #define CPP2_FORCE_INLINE __attribute__((always_inline)) #if defined(__clang__) #define CPP2_FORCE_INLINE_LAMBDA /* empty */ #undef CPP2_FORCE_INLINE_LAMBDA_CLANG #define CPP2_FORCE_INLINE_LAMBDA_CLANG __attribute__((always_inline)) #else #define CPP2_FORCE_INLINE_LAMBDA __attribute__((always_inline)) #endif #if defined(__clang_major__) // Also check __cplusplus, only to satisfy Clang -pedantic-errors #if __cplusplus >= 202302L && (__clang_major__ > 13 || (__clang_major__ == 13 && __clang_minor__ >= 2)) #define CPP2_LAMBDA_NO_DISCARD [[nodiscard]] #else #define CPP2_LAMBDA_NO_DISCARD #endif #elif defined(__GNUC__) #if __GNUC__ >= 9 #define CPP2_LAMBDA_NO_DISCARD [[nodiscard]] #else #define CPP2_LAMBDA_NO_DISCARD #endif #if ((__GNUC__ * 100) + __GNUC_MINOR__) < 1003 // GCC 10.2 doesn't support this feature (10.3 is fine) #undef CPP2_FORCE_INLINE_LAMBDA #define CPP2_FORCE_INLINE_LAMBDA #endif #else #define CPP2_LAMBDA_NO_DISCARD #endif #endif namespace cpp2 { //----------------------------------------------------------------------- // // Convenience names for fundamental types // // Note: De jure, some of these are optional per the C and C++ standards // De facto, all of these are supported in all implementations I know of // //----------------------------------------------------------------------- // // Encouraged by default: Fixed-precision names using i8 = std::int8_t ; using i16 = std::int16_t ; using i32 = std::int32_t ; using i64 = std::int64_t ; using u8 = std::uint8_t ; using u16 = std::uint16_t ; using u32 = std::uint32_t ; using u64 = std::uint64_t ; // Discouraged: Variable precision names // short using ushort = unsigned short; // int using uint = unsigned int; // long using ulong = unsigned long; using longlong = long long; using ulonglong = unsigned long long; using longdouble = long double; // Strongly discouraged, for compatibility/interop only using _schar = signed char; // normally use i8 instead using _uchar = unsigned char; // normally use u8 instead //----------------------------------------------------------------------- // // String utilities // namespace string_util { // Break a string_view into a vector of views of simple qidentifier // substrings separated by other characters inline auto split_string_list(std::string_view str) -> std::vector { std::vector ret; auto is_id_char = [](char c) { return std::isalnum(c) || c == '_'; }; auto pos = decltype(std::ssize(str)){ 0 }; while( pos < std::ssize(str) ) { // Skip non-alnum while (pos < std::ssize(str) && !is_id_char(str[pos])) { ++pos; } auto start = pos; // Find the end of the current component while (pos < std::ssize(str) && is_id_char(str[pos])) { ++pos; } // Add nonempty substring to the vector if (start < pos) { ret.emplace_back(str.substr(start, pos - start)); } } return ret; } // From https://stackoverflow.com/questions/216823/how-to-trim-a-stdstring // Trim from start (in place) inline void ltrim(std::string &s) { s.erase( s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }) ); } // Trim from end (in place) inline void rtrim(std::string &s) { s.erase( std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end() ); } // Trim from both ends (in place) inline void trim(std::string &s) { rtrim(s); ltrim(s); } // Trim from both ends (copying) inline std::string trim_copy(std::string_view s) { std::string t(s); trim(t); return t; } // From https://oleksandrkvl.github.io/2021/04/02/cpp-20-overview.html#nttp template struct fixed_string { constexpr fixed_string(const CharT (&s)[N+1]) { std::copy_n(s, N + 1, c_str); } constexpr const CharT* data() const { return c_str; } constexpr std::size_t size() const { return N; } constexpr auto str() const { return std::basic_string(c_str); } CharT c_str[N+1]; }; template fixed_string(const CharT (&)[N])->fixed_string; // Other string utility functions. constexpr bool is_escaped(std::string_view s) { return s.starts_with("\"") && s.ends_with("\"") ; } inline bool string_to_int(std::string const& s, int& v, int base = 10) { try { v = stoi(s, nullptr, base); return true; } catch (std::invalid_argument const&) { return false; } catch (std::out_of_range const&) { return false; } } template inline std::string int_to_string(int i) { if constexpr (8 == Base) { std::ostringstream oss; oss << std::oct << i; return oss.str(); } else if constexpr (10 == Base) { return std::to_string(i); } else if constexpr (16 == Base) { std::ostringstream oss; oss << std::hex << i; return oss.str(); } else { [] () { static_assert(flag, "Unsupported int_to_string Base"); }(); } } inline char safe_toupper(char ch) { return static_cast(std::toupper(static_cast(ch))); } inline char safe_tolower(char ch) { return static_cast(std::tolower(static_cast(ch))); } inline std::string replace_all( std::string str, const std::string& from, const std::string& to ) { size_t start_pos = 0; while((start_pos = str.find(from, start_pos)) != std::string::npos) { str.replace(start_pos, from.length(), to); start_pos += to.length(); // safe also when 'to' is a substring of 'from' } return str; } template inline std::string join(List const& list) { std::string r = ""; std::string sep = ""; for (auto const& cur : list) { r += sep + cur; sep = ", "; } return r; } } // namespace string_util //----------------------------------------------------------------------- // // Conveniences for expressing Cpp1 references (rarely useful) // // Note: Only needed in rare cases to take full control of matching an // odd Cpp1 signature exactly. Most cases don't need this... for // example, a Cpp1 virtual function signature declaration like // // virtual void myfunc(int& val) const // // can already be directly overriden by a Cpp2 declaration of // // myfunc: (override this, inout val: int) // // identical to this in Cpp1 syntax: // // void myfunc(int& val) const override // // without any need to say cpp1_ref on the int parameter. // //----------------------------------------------------------------------- // template using cpp1_ref = std::add_lvalue_reference_t; template using cpp1_rvalue_ref = std::add_rvalue_reference_t; //----------------------------------------------------------------------- // // Helper for concepts // //----------------------------------------------------------------------- // template auto argument_of_helper(Ret(*) (Arg)) -> Arg; template auto argument_of_helper(Ret(F::*) (Arg)) -> Arg; template auto argument_of_helper(Ret(F::*) (Arg)&) -> Arg; template auto argument_of_helper(Ret(F::*) (Arg)&&) -> Arg; template auto argument_of_helper(Ret(F::*) (Arg) const) -> Arg; template auto argument_of_helper(Ret(F::*) (Arg) const&) -> Arg; template auto argument_of_helper(Ret(F::*) (Arg) const&&) -> Arg; template auto argument_of_helper(F const&) -> CPP2_TYPEOF(argument_of_helper(&F::operator())); template using argument_of_t = CPP2_TYPEOF(argument_of_helper(std::declval())); template auto argument_of_helper_op_is(F const&) -> CPP2_TYPEOF(argument_of_helper(&F::op_is)); template using argument_of_op_is_t = CPP2_TYPEOF(argument_of_helper_op_is(std::declval())); template using pointee_t = std::iter_value_t; template