[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/iopenstack/cppfront/main/include/cpp2util.h [Back]  [Original]

//  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  boolean_testable;} && ...)) {
            ((CPP2_FORWARD(fun)(type_it{}) && (found = Is, true)) || ...);
        } 
    }(std::index_sequence_for());
    return found;
}

template 
constexpr auto type_find_if(C const&, F&& fun)
{
    return type_find_if(CPP2_FORWARD(fun));
}

template 
constexpr auto variant_contains_type(std::variant)
{
    if constexpr (is_any) {
        return std::true_type{};
    } else {
        return std::false_type{};
    }
}

template 
using constness_like_t =
  std::conditional_t<
    std::is_const_v<
      std::remove_pointer_t<
        std::remove_reference_t
      >
    >,
    std::add_const_t,
    std::remove_const_t
  >;

template
[[nodiscard]] constexpr auto forward_like(U&& x) noexcept -> decltype(auto)
{
    constexpr bool is_adding_const = std::is_const_v;
    if constexpr (std::is_lvalue_reference_v)
    {
        if constexpr (is_adding_const)
            return std::as_const(x);
        else
            return static_cast(x);
    }
    else
    {
        if constexpr (is_adding_const)
            return std::move(std::as_const(x));
        else
            return std::move(x);
    }
}


//-----------------------------------------------------------------------
//
//  contract_group
//
//-----------------------------------------------------------------------
//

#ifdef CPP2_USE_SOURCE_LOCATION
    #define CPP2_SOURCE_LOCATION_PARAM              , [[maybe_unused]] std::source_location where
    #define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT , [[maybe_unused]] std::source_location where = std::source_location::current()
    #define CPP2_SOURCE_LOCATION_PARAM_SOLO         [[maybe_unused]] std::source_location where
    #define CPP2_SOURCE_LOCATION_ARG                , where
    #define CPP2_SOURCE_LOCATION_VALUE              (cpp2::to_string(where.file_name()) + "(" + cpp2::to_string(where.line()) + ") " + where.function_name())
#else
    #define CPP2_SOURCE_LOCATION_PARAM
    #define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT
    #define CPP2_SOURCE_LOCATION_PARAM_SOLO
    #define CPP2_SOURCE_LOCATION_ARG
    #define CPP2_SOURCE_LOCATION_VALUE              std::string("")
#endif

//  For C++23: make this std::string_view and drop the macro
//      Before C++23 std::string_view was not guaranteed to be trivially copyable,
//      and so in will pass it by const& and really it should be by value
#define CPP2_MESSAGE_PARAM  char const*
#define CPP2_CONTRACT_MSG   cpp2::message_to_cstr_adapter

inline auto message_to_cstr_adapter( CPP2_MESSAGE_PARAM msg ) -> CPP2_MESSAGE_PARAM { return msg ? msg : ""; }
inline auto message_to_cstr_adapter( std::string const& msg ) -> CPP2_MESSAGE_PARAM { return msg.c_str(); }

class contract_group {
public:
    using handler = void (*)(CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM);

    constexpr contract_group  (handler h = {}) : reporter{h} { }
    constexpr auto set_handler(handler h = {}) { reporter = h; }
    constexpr auto is_active  () const -> bool    { return reporter != handler{}; }

    constexpr auto enforce(bool b, CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT)
                                          -> void { if (!b) report_violation(msg CPP2_SOURCE_LOCATION_ARG); }
    constexpr auto report_violation(CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT)
                                          -> void { if (reporter) reporter(msg CPP2_SOURCE_LOCATION_ARG); }
private:
    handler reporter;
};

[[noreturn]] inline auto report_and_terminate(std::string_view group, CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) noexcept -> void {
    std::cerr
#ifdef CPP2_USE_SOURCE_LOCATION
        ';
#endif
    auto pos = name.find(type_prefix);
    if (pos != name.npos) {
        name = name.substr(pos);
        name.remove_prefix(type_prefix.size());
    }

    pos = name.find_last_of(types_close_parenthesis);
    if (pos != name.npos) {
        name = name.substr(0, pos);
    }

#if defined(__GNUC__)
    constexpr auto type_separator = ';';
    pos = name.find(type_separator);
    if (pos != name.npos) {
        name = name.substr(0, pos);
    }
#endif

    return name;
}

template
constexpr auto type_name() -> std::string_view {
#if defined(__clang__) || defined(__GNUC__)    
    constexpr auto ret = process_type_name(__PRETTY_FUNCTION__);
#elif defined(_MSC_VER)
    constexpr auto ret = process_type_name(__FUNCSIG__);
#else
    constexpr auto ret = "";
#endif
    return ret;
}

#endif 

//-----------------------------------------------------------------------
//
//  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 \""};
 
    #ifdef CPP2_NO_RTTI
    err += type_name();
    #else 
    err += typeid(decltype(x)).name();
    #endif
    err += "\"";
    if (msg) {
        err += std::string{" and the message \""} + msg + "\"";
    }
    type_safety.report_violation( err.c_str() );
    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
constexpr auto Dynamic_cast( [[maybe_unused]] auto&& x ) -> decltype(auto) {
#ifdef CPP2_NO_RTTI
    type_safety.report_violation( "'as' dynamic casting is disabled with -fno-rtti" );
    return nullptr;
#else
    return dynamic_cast(CPP2_FORWARD(x));
#endif
}

template
constexpr auto Typeid() -> decltype(auto) {
#ifdef CPP2_NO_RTTI
    type_safety.report_violation( "'any' dynamic casting is disabled with -fno-rtti" );
#else
    return typeid(T);
#endif
}

constexpr auto Typeid( [[maybe_unused]] auto&& x ) -> decltype(auto) {
#ifdef CPP2_NO_RTTI
    type_safety.report_violation( "'typeid' is disabled with -fno-rtti" );
#else
    return typeid(CPP2_FORWARD(x));
#endif
}

} // impl


//-----------------------------------------------------------------------
//
//  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)...);
}


namespace impl {

//-----------------------------------------------------------------------
//
//  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:
    constexpr  deferred_init() noexcept       { }
    constexpr ~deferred_init() noexcept       { destroy(); }
    constexpr auto value()    noexcept -> T& { cpp2_default.enforce(init);  return t(); }

    constexpr auto construct(auto&& ...args) -> void { cpp2_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:
    constexpr out(T*                 t_) noexcept :  t{ t_}, has_t{true}       { cpp2_default.enforce( t); }
    constexpr out(deferred_init* dt_) noexcept : dt{dt_}, has_t{false}      { cpp2_default.enforce(dt); }
    constexpr out(out*           ot_) noexcept : ot{ot_}, has_t{ot_->has_t} { cpp2_default.enforce(ot);
        if (has_t) {  t = ot->t;  }
        else       { dt = ot->dt; }
    }

    constexpr 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)
    constexpr ~out() {
        if (called_construct() && uncaught_count != Uncaught_exceptions()) {
            cpp2_default.enforce(!has_t);
            dt->destroy();
            called_construct() = false;
        }
    }

    constexpr auto construct(auto&& ...args) -> void {
        if (has_t || called_construct()) {
            if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) {
                cpp2_default.enforce( t );
                *t = T(CPP2_FORWARD(args)...);
            }
            else {
                cpp2_default.report_violation("attempted to copy assign, but copy assignment is not available");
            }
        }
        else {
            cpp2_default.enforce( dt );
            if (dt->init) {
                if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) {
                    dt->value() = T(CPP2_FORWARD(args)...);
                }
                else {
                    cpp2_default.report_violation("attempted to copy assign, but copy assignment is not available");
                }
            }
            else {
                dt->construct(CPP2_FORWARD(args)...);
                called_construct() = true;
            }
        }
    }

    constexpr auto value() noexcept -> T& {
        if (has_t) {
            cpp2_default.enforce( t );
            return *t;
        }
        else {
            cpp2_default.enforce( dt );
            return dt->value();
        }
    }
};


} // impl


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

//  Suppress spurious MSVC warnings about unreachable code
#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4702 )
#endif
inline auto to_string(auto const& x) -> std::string
{
    //  Handle degenerate case
    if constexpr (std::is_same_v) {
        return x;
    }

    //  Else customize convertible-to-bool - use { } to avoid narrowing
    if constexpr( requires{ bool{x}; } ) 
    {
        return x ? "true" : "false";
    }

    //  Else customize char (before int)
    if constexpr (std::is_same_v) {
        return std::string{ x };
    }

    //  Else customize char*
    if constexpr (std::is_same_v) {
        return std::string{ x };
    }

    //  Else prefer string_view if available
    if constexpr (std::is_convertible_v) {
        return std::string{ x };
    }

    //  Else prefer std::to_string if available
    if constexpr( requires { std::to_string(x); } ) {
        return std::to_string(x);
    }

    //  Else prefer streaming  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(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


namespace impl {

//-----------------------------------------------------------------------
//
//  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( X&& ) {
    if constexpr (specialization_of_template) {
        return std::true_type{};
    }
    else {
        return std::false_type{};
    }
}

template 
constexpr auto is( X&& ) {
    if constexpr (specialization_of_template_type_and_nttp) {
        return std::true_type{};
    }
    else {
        return std::false_type{};
    }
}

//  Types
//
template< typename C, typename X >
constexpr auto is( X const& x ) -> auto {
    if constexpr (
        std::is_same_v
        || std::is_base_of_v
    )
    {
        return std::true_type{};
    }
    else if constexpr (
        std::is_polymorphic_v
        && std::is_polymorphic_v
    )
    {
        return Dynamic_cast(&x) != nullptr;
    }
    else if constexpr (
        (
            std::is_same_v
            || requires { *x; X(); }
        )
        && std::is_same_v
    )
    {
        return x == X();
    }
    else if constexpr (
        std::is_pointer_v
        && std::is_pointer_v
    )
    {
        if (x != nullptr) {
            return bool{is(*x)};
        }
        return false;
    }
    else {
        return std::false_type{};
    }
}


//  Values
//
constexpr auto is( auto const& x, auto&& value ) -> bool
{
    //  Value with customized operator_is case
    if constexpr (valid_custom_is_operator) {
        return x.op_is(value);
    }

    //  Predicate case
    else if constexpr (valid_predicate) {
        return value(x);
    }

    //  Value equality case: C/C++ arrays or individual values
    else if constexpr (std::is_array_v && std::is_array_v) {
        if (std::ssize(x) == std::ssize(value)) {
            return std::equal( std::begin(x), std::end(x), std::begin(value));
        }
        return false;
    }
    else if constexpr (requires{ bool{x == value}; }) {
        return x == value;
    }
    return false;
}

//-----------------------------------------------------------------------
//
//  and "is predicate" for generic function used as predicate
//

template 
constexpr auto is( X const& x, bool (*value)(X const&) ) -> bool {
    return value(x);
}

//-------------------------------------------------------------------------------------------------------------
//  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 >
constexpr auto is_narrowing_v =
    // [dcl.init.list] 7.1
    (std::is_floating_point_v && std::is_integral_v) ||
    // [dcl.init.list] 7.2
    (std::is_floating_point_v && std::is_floating_point_v && sizeof(From) > sizeof(To)) || // NOLINT(misc-redundant-expression)
    // [dcl.init.list] 7.3
    (std::is_integral_v && std::is_floating_point_v) ||
    (std::is_enum_v && std::is_floating_point_v) ||
    // [dcl.init.list] 7.4
    (std::is_integral_v && std::is_integral_v && sizeof(From) > sizeof(To)) || // NOLINT(misc-redundant-expression)
    (std::is_enum_v && std::is_integral_v && sizeof(From) > sizeof(To)) ||
    // [dcl.init.list] 7.5
    (std::is_pointer_v && std::is_same_v)
    ;

template< typename To, typename From >
constexpr auto is_unsafe_pointer_conversion_v =
    std::is_pointer_v
    && std::is_pointer_v
// Work around Clang 
    requires (std::is_arithmetic_v && std::is_arithmetic_v)
constexpr auto as() -> auto
{
    if constexpr ( is_castable_v ) {
        return static_cast(x);
    } else {
        return nonesuch;
    }
}

template< typename C, auto x >
    requires (std::is_same_v && std::is_integral_v)
constexpr auto as() -> auto
{
    return cpp2::to_string(CPP2_FORWARD(x));
}

//  Work around MSVC modules bugs: source_location doesn't work correctly if imported via a module
#if defined(_MSC_VER) && defined(CPP2_IMPORT_STD)
    #define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT_AS
    #define CPP2_SOURCE_LOCATION_ARG_AS
#else
    #define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT_AS    CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT
    #define CPP2_SOURCE_LOCATION_ARG_AS                   CPP2_SOURCE_LOCATION_ARG
#endif
template< typename C >
constexpr auto as(auto&& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT_AS) -> decltype(auto)
    //  This "requires" list may need to be tweaked further. The idea is to have
    //  this function used for all the cases it's supposed to cover, but not
    //  hide user-supplied extensions (such as the ones later in this file for
    //  std:: polymorphic types like any/optional/variant)
    requires (
                (std::is_scalar_v && !std::is_enum_v)
            ||  std::is_floating_point_v
            ||  std::is_base_of_v
            ||  std::is_base_of_v
            ||  requires { C{CPP2_FORWARD(x)}; }
            )
{
    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_AS
        );
        return CPP2_COPY(c);
    }
    else if constexpr (std::is_same_v && std::is_integral_v) {
        return cpp2::to_string(CPP2_FORWARD(x));
    }
    else if constexpr (std::is_same_v) {
        return CPP2_FORWARD(x);
    }
    else if constexpr (std::is_base_of_v) {
        if constexpr (std::is_const_v) {
            return static_cast(CPP2_FORWARD(x));
        } else {
            return static_cast(CPP2_FORWARD(x));
        }
    }
    else if constexpr (std::is_base_of_v) {
        if constexpr (std::is_const_v) {
            return Dynamic_cast(CPP2_FORWARD(x));
        } else {
            return Dynamic_cast(CPP2_FORWARD(x));
        }
    }
    else if constexpr (
        std::is_pointer_v
        && std::is_pointer_v
        && requires { requires std::is_base_of_v; }
    )
    {
        return Dynamic_cast(CPP2_FORWARD(x));
    }
    else if constexpr (
        is_unsafe_pointer_conversion_v
        )
    {
        return nonesuch;
    }
    else if constexpr (requires { C{CPP2_FORWARD(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{CPP2_FORWARD(x)};
    }
    else {
        return nonesuch;
    }
}


//-------------------------------------------------------------------------------------------------------------
//  std::variant is and as
//

template< typename C, specialization_of_template X >
constexpr auto is( X const& x ) -> auto
{
    if constexpr (
        std::is_same_v
        || std::is_base_of_v
    )
    {
        return std::true_type{};
    }
    else {
        if (x.valueless_by_exception()) {
            return std::is_same_v;
        }
        if constexpr (
            std::is_same_v
        )
        {
            if constexpr (requires { {variant_contains_type(std::declval())} -> std::same_as; }) {
                return std::get_if(&x) != nullptr;
            }
        }
        return type_find_if(x, [&](It const&) -> bool {
            if (x.index() == It::index) { return std::is_same_v;}
            return false;
        }) != std::variant_npos;
    }
}


template 
constexpr auto is( X const& x, auto&& value ) -> bool
{
    return type_find_if(x, [&](It const&) -> bool {
        if (x.index() == It::index) {
            if constexpr (valid_predicate) {
                return value(std::get(x));
            } else if constexpr ( requires { bool{std::get(x) == value}; }  ) {
                return std::get(x) == value;
            }
        }
        return false;
    }) != std::variant_npos;
}

template< typename C, specialization_of_template X >
auto as(X&& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT_AS) -> decltype(auto)
{
    constness_like_t* ptr = nullptr;
    type_find_if(CPP2_FORWARD(x), [&](It const&) -> bool {
        if constexpr (std::is_same_v< typename It::type, C >) { if (CPP2_FORWARD(x).index() ==  It::index) { ptr = &std::get(x); return true; } }; 
        return false;
    });
    if (!ptr) { Throw( std::bad_variant_access(), "'as' cast failed for 'variant'"); }
    return cpp2::forward_like(*ptr);
}

//-------------------------------------------------------------------------------------------------------------
//  std::any is and as
//

//  is Type
//
template
constexpr auto is( X const& x ) -> bool{
    if (!x.has_value()) {
        return std::is_same_v;
    }
    return x.type() == Typeid(); 
}

//  is Value
//
constexpr auto is( std::any const& x, auto&& value ) -> bool
{
    //  Predicate case
    if constexpr (valid_predicate) {
        return value(x);
    }

    //  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
constexpr auto as( X && x ) -> decltype(auto) {
    constness_like_t* ptr = std::any_cast( &x );
    if (!ptr) { Throw( std::bad_any_cast(), "'as' cast failed for 'std::any'"); }
    return cpp2::forward_like(*ptr);
}


//-------------------------------------------------------------------------------------------------------------
//  std::optional is and as
//

//  is Type
//
template
constexpr auto is( X const& x ) -> bool { 
    if (!x.has_value()) {
        return std::same_as;
    }
    if constexpr (requires { static_cast(*x);}) {
        return true;
    }
    return false;
}

//  is Value
//
template
constexpr auto is( std::optional const& x, auto&& value ) -> bool
{
    //  Predicate case
    if constexpr (valid_predicate) {
        return value(x);
    }

    //  Value case
    else if constexpr (requires{ bool{ x.value() == value }; }) {
        return x.has_value() && x.value() == value;
    }
    return false;
}


//  as
//
template
constexpr auto as( X&& x ) -> decltype(auto) { 
    constness_like_t* ptr = nullptr;
    if constexpr (requires { static_cast(*x); }) {
        if (x.has_value()) {
            ptr = &static_cast(*x);
        }
    }
    if (!ptr) { Throw( std::bad_optional_access(), "'as' cast failed for 'std::optional'"); }
    return cpp2::forward_like(*ptr);
}


} // impl


//-----------------------------------------------------------------------
//
//  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:
    constexpr explicit finally_success(const F& ff) noexcept : f{ff} { }
    constexpr explicit finally_success(F&& ff) noexcept : f{std::move(ff)} { }

    constexpr ~finally_success() noexcept
    {
        if (invoke && ecount == std::uncaught_exceptions()) {
            f();
        }
    }

    constexpr 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:
    constexpr explicit finally(const F& ff) noexcept : f{ff} { }
    constexpr explicit finally(F&& ff) noexcept : f{std::move(ff)} { }

    constexpr ~finally() noexcept { f(); }

    constexpr 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;
};


//-----------------------------------------------------------------------
//
//  An implementation of GSL's narrow_cast with a clearly 'unchecked' name
//
//-----------------------------------------------------------------------
//
template 
constexpr auto unchecked_narrow( X x ) noexcept 
    -> decltype(auto)
    requires (
        impl::is_narrowing_v
        || (
            std::is_arithmetic_v
            && std::is_arithmetic_v
            )
            )
{
    return static_cast(x);
}


template 
constexpr auto unchecked_cast( X&& x ) noexcept 
    -> decltype(auto)
{
    return static_cast(CPP2_FORWARD(x));
}


//-----------------------------------------------------------------------
//
//  args: see main() arguments as a container of string_views
//
//  Does not perform any dynamic memory allocation - each string_view
//  is directly bound to the string provided by the host environment
// 
//  Note: These string_views happen to be null-terminated. We ought
//        to also have a std::zstring_view to express that...
//
//-----------------------------------------------------------------------
//
struct args
{
    constexpr args(int c, char** v) : argc{c}, argv{v} {}

    class iterator {
    public:
        constexpr iterator(int c, char** v, int start) : argc{c}, argv{v}, curr{start} {}

        constexpr auto operator*() const {
            if (curr < argc) { return std::string_view{ argv[curr] }; }
            else             { return std::string_view{}; }
        }

        constexpr auto operator+(int i) -> iterator  {
            if (i > 0) { return { argc, argv, std::min(curr+i, argc) }; }
            else       { return { argc, argv, std::max(curr+i, 0   ) }; }
        }
        constexpr auto operator-(int i) -> iterator  { return operator+(-i); }
        constexpr auto operator++()     -> iterator& { curr = std::min(curr+1, argc);  return *this; }
        constexpr auto operator--()     -> iterator& { curr = std::max(curr-1, 0   );  return *this; }
        constexpr auto operator++(int)  -> iterator  { auto old = *this;  ++*this;  return old; }
        constexpr auto operator--(int)  -> iterator  { auto old = *this;  ++*this;  return old; }

        constexpr auto operator(iterator const&) const = default;

    private:
        int    argc;
        char** argv;
        int    curr;
    };

    constexpr auto begin()  const -> iterator       { return iterator{ argc, argv, 0    }; }
    constexpr auto end()    const -> iterator       { return iterator{ argc, argv, argc }; }
    constexpr auto cbegin() const -> iterator       { return begin(); }
    constexpr auto cend()   const -> iterator       { return end(); }
    constexpr auto size()   const -> std::size_t    { return cpp2::unchecked_narrow(ssize()); }
    constexpr auto ssize()  const -> std::ptrdiff_t { return argc; }

    constexpr auto operator[](int i) const {
        if (0  args
{
    return args{argc, argv};
}


//-----------------------------------------------------------------------
//
//  range: a range of [begin, end) or [first, last]
//
//  TT is the type we actually store for 'first' and 'last'.
// 
//  If T is integral, store a widened representation to ensure that
//  the past-the-end value is representable even if [first,last] are
//  numeric_limits [min,max].
// 
//  This lets us represent all ranges as half-open ranges using just
//  'first' and (possibly-adjusted-by-one) 'last' without any extra
//  data or a Closed parameter etc. = single simpler implementation.
//
//-----------------------------------------------------------------------
//
template
class range
{
    using TT = std::conditional_t<
        std::is_integral_v, 
        std::conditional_t<
            std::is_signed_v, 
            std::ptrdiff_t, 
            std::size_t
        >, 
        T
    >;

    TT first;
    TT last;

public:
    using difference_type = std::ptrdiff_t;
    using value_type      = T;
    using pointer         = T*;
    using reference       = T&;

    constexpr range(
        T const&                       f,
        std::type_identity_t const& l,
        bool                           include_last = false
    )
        //  For smaller-than-size_t/ptrdiff_t numeric types, these will widen
        : first{ f }
        , last{ l }
    {
        //  Represent all ranges as half-open; after this we can forget the flag
        if (include_last) { 
            if constexpr (std::integral) {
                if (last == std::numeric_limits::max()) {
                    throw std::runtime_error(
                        "range with last == numeric_limits::max() will overflow"
                    );
                }
            }
            ++last; 
        }
    }

    //  When the first & last types are different, use a CTAD deduction guide
    //  to find the `std::common_type` for them, if one exists. See below
    //  after the class definition for the deduction guide.
    template 
        requires has_common_type
    range(
        T const& f,
        U const& l,
        bool     include_last = false
    )
        : range(f, l, include_last)
    {}

    class iterator 
    {
        TT first = T{};
        TT last  = T{};
        TT curr  = T{};

        // Helper type trait to check for the existence of iterator_category
        template 
        struct range_iterator_category {
            using tag = std::random_access_iterator_tag;
        };
        template 
        struct range_iterator_category {
            using tag = typename std::iterator_traits::iterator_category;
        };

    public:
        using difference_type   = std::ptrdiff_t;
        using value_type        = T;
        using pointer           = T*;
        using reference         = T&;
        using iterator_category = typename range_iterator_category::tag;

        constexpr iterator() { }

        constexpr iterator(TT const& f, TT const& l, TT start) : first{ f }, last{ l }, curr{ start } {}

        auto operator(iterator const&) const = default;

        constexpr operator typename range::iterator() const { return {first, last, curr}; }

        //  In this section, we don't use relational comparisons so that
        //  this works when T is a less-powerful-than-random-access iterator
        //
        constexpr auto operator*() const -> T
        {
            if (curr != last) { 
                if constexpr (std::is_same_v) { 
                    return curr;
                }
                else {
                    return unchecked_narrow(curr);
                }
            }
            else { 
                return T{};
            }
        }

        constexpr auto operator++()    -> iterator& { if (curr != last ) { ++curr; }  return *this; }
        constexpr auto operator--()    -> iterator& { if (curr != first) { --curr; }  return *this; }
        constexpr auto operator++(int) -> iterator  { auto old = *this;  ++*this;  return old; }
        constexpr auto operator--(int) -> iterator  { auto old = *this;  ++*this;  return old; }

        //  And now all the random-access operations which can use relational
        //  comparisons (these functions are valid if T is random-access)
        //
        constexpr auto operator[](difference_type i) const -> T {
            if (curr + i != last) { 
                if constexpr (std::is_same_v) { 
                    return curr + i;
                }
                else {
                    return unchecked_narrow(curr + i);
                }
            }
            else {
                return T{};
            }
        }

        constexpr auto operator+=(difference_type i) -> iterator& 
            { if (curr + i  iterator& 
            { if (curr - i >= first) { curr -= i; } else { curr = first; }  return *this; }

        friend 
        constexpr auto operator+ (difference_type i, iterator const& iter) -> iterator 
            { auto ret = *iter;  return ret += i; }

        constexpr auto operator+ (difference_type i   ) const -> iterator        { auto ret = *this;  return ret += i; }
        constexpr auto operator- (difference_type i   ) const -> iterator        { auto ret = *this;  return ret -= i; }
        constexpr auto operator- (iterator        that) const -> difference_type { return that.curr - curr; }
    };

    using const_iterator = typename range::iterator;

    constexpr auto cbegin() const -> const_iterator { return begin(); }
    constexpr auto cend()   const -> const_iterator { return end(); }
    constexpr auto begin()  const -> const_iterator { return iterator{ first, last, first }; }
    constexpr auto end()    const -> const_iterator { return iterator{ first, last, last }; }
    constexpr auto begin()        -> iterator       { return iterator{ first, last, first }; }
    constexpr auto end()          -> iterator       { return iterator{ first, last, last }; }
    constexpr auto size()   const -> std::size_t    { return unchecked_narrow(ssize()); }
    constexpr auto ssize()  const -> std::ptrdiff_t { return last - first; }
    constexpr auto empty()  const -> bool           { return first == last; }

    constexpr auto front() const -> T { 
        type_safety.enforce(!empty()); 
        if constexpr (std::is_same_v) { 
            return first;
        }
        else {
            return unchecked_narrow(first);
        }
    }

    constexpr auto back() const -> T { 
        type_safety.enforce(!empty()); 
        if constexpr (std::is_same_v) { 
            auto ret = last; 
            return --ret;
        }
        else {
            auto ret = unchecked_narrow(last);
            return --ret;
        }
    }

    constexpr auto operator[](difference_type i) const -> T
    {
        if (0  range;

template
constexpr auto contains(range const& r, auto const& t)
    -> bool
{
    if (r.empty()) {
        return false;
    }
    return r.front()  decltype(auto)
    requires requires {CPP2_FORWARD(t) < CPP2_FORWARD(u);}
{
    cmp_mixed_signedness_check();
    return CPP2_FORWARD(t) < CPP2_FORWARD(u);
}

CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u) 
    -> decltype(auto)
{
    static_assert(
        program_violates_type_safety_guarantee,
        "attempted to compare '' for incompatible types"
        );
    return nonesuch;
}


CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u) 
    -> decltype(auto)
    requires requires {CPP2_FORWARD(t) >= CPP2_FORWARD(u);}
{
    cmp_mixed_signedness_check();
    return CPP2_FORWARD(t) >= CPP2_FORWARD(u);
}

CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u) 
    -> decltype(auto)
{
    static_assert(
        program_violates_type_safety_guarantee,
        "attempted to compare '>=' for incompatible types"
        );
    return nonesuch;
}


//-----------------------------------------------------------------------
//
//  A static-asserting "as" for better diagnostics than raw 'nonesuch'
//
//  Note for the future: This needs go after all 'as', which is fine for
//  the ones in this file but will have problems with further user-
//  defined 'as' customizations. One solution would be to make the main
//  'as' be a class template, and have all customizations be actual
//  specializations... that way name lookup should find the primary
//  template first and then see later specializations. Or we could just
//  remove this and live with the 'nonesuch' error messages. Either way,
//  we don't need anything more right now, this solution is fine to
//  unblock general progress
//
//-----------------------------------------------------------------------
//
template< typename C >
constexpr auto as_( auto&& x ) -> decltype(auto)
{
    if constexpr (is_narrowing_v) {
        static_assert(
            program_violates_type_safety_guarantee,
            "'as' does not allow unsafe possibly-lossy narrowing conversions - if you're sure you want this, use 'unchecked_narrow' to explicitly force the conversion and possibly lose information"
        );
    }
    else if constexpr (is_unsafe_pointer_conversion_v)
    {
        static_assert(
            program_violates_type_safety_guarantee,
            "'as' does not allow unsafe pointer conversions - if you're sure you want this, use `unchecked_cast()` to explicitly force the cast"
            );
    }
    else if constexpr( std::is_same_v< CPP2_TYPEOF(as(CPP2_FORWARD(x))), nonesuch_ > ) {
        static_assert(
            program_violates_type_safety_guarantee,
            "No safe 'as' cast available - please check your cast"
        );
    }
    //  else
    return as(CPP2_FORWARD(x));
}

template< typename C, auto x >
constexpr auto as_() -> decltype(auto)
{
    if constexpr (requires { as(); }) {
        if constexpr( std::is_same_v< CPP2_TYPEOF((as())), nonesuch_ > ) {
            static_assert(
                program_violates_type_safety_guarantee,
                "'as' does not allow unsafe possibly-lossy narrowing conversions - if you're sure you want this, use `unchecked_narrow()` to explicitly force the conversion and possibly lose information"
                );
        }
    }
    else {
        static_assert(
            program_violates_type_safety_guarantee,
            "No safe 'as' cast available - please check your cast"
        );
    }
    //  else
    return as();
}

} // impl


}

using cpp2::cpp2_new;


//  Stabilize line numbers for "compatibility" static assertions
//  and UFCS error output to keep regression test outputs cleaner
#line 9999

//-----------------------------------------------------------------------
//
//  CPP2_UFCS: Variadic macro generating a variadic lambda, oh my...
//
//-----------------------------------------------------------------------
//

#define CPP2_UFCS_EMPTY(...)
#define CPP2_UFCS_IDENTITY(...)  __VA_ARGS__
#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__

// Ideally, the expression `CPP2_UFCS_IS_NOTHROW` expands to
// is in the _noexcept-specifier_ of the UFCS lambda, but without 'std::declval'.
// To workaround [GCC bug 101043](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101043),
// we instead make it a template parameter of the UFCS lambda.
// But using a template parameter, Clang also ICEs on an application.
// So we use these `NOTHROW` macros to fall back to the ideal for when not using GCC.
#define CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,...) \
   requires { requires  requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); }; \
              requires    noexcept(std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...)); } \
|| requires { requires !requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); }; \
              requires noexcept(MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(std::declval(), std::declval()...)); }
#define CPP2_UFCS_IS_NOTHROW_PARAM(...)                     /*empty*/
#define CPP2_UFCS_IS_NOTHROW_ARG(MVFWD,QUALID,TEMPKW,...)   CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,__VA_ARGS__)
#if defined(__GNUC__) && !defined(__clang__)
    #undef  CPP2_UFCS_IS_NOTHROW_PARAM
    #undef  CPP2_UFCS_IS_NOTHROW_ARG
    #define CPP2_UFCS_IS_NOTHROW_PARAM(MVFWD,QUALID,TEMPKW,...) , bool IsNothrow = CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,__VA_ARGS__)
    #define CPP2_UFCS_IS_NOTHROW_ARG(...)                       IsNothrow
    #if __GNUC__ < 11
        #undef  CPP2_UFCS_IS_NOTHROW_PARAM
        #undef  CPP2_UFCS_IS_NOTHROW_ARG
        #define CPP2_UFCS_IS_NOTHROW_PARAM(...)    /*empty*/
        #define CPP2_UFCS_IS_NOTHROW_ARG(...)      false // GCC 10 UFCS is always potentially-throwing.
    #endif
#endif

// Ideally, the expression `CPP2_UFCS_CONSTRAINT_ARG` expands to
// is in the _requires-clause_ of the UFCS lambda.
// To workaround an MSVC bug within a member function 'F' where UFCS is also for 'F'
// (),
// we instead make it a template parameter of the UFCS lambda.
// But using a template parameter, Clang also ICEs and GCC rejects a local 'F'.
// Also, Clang rejects the SFINAE test case when using 'std::declval'.
// So we use these `CONSTRAINT` macros to fall back to the ideal for when not using MSVC.
#define CPP2_UFCS_CONSTRAINT_PARAM(...)                   /*empty*/
#define CPP2_UFCS_CONSTRAINT_ARG(MVFWD,QUALID,TEMPKW,...) \
   requires { CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); } \
|| requires { MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); }
#if defined(_MSC_VER)
    #undef  CPP2_UFCS_CONSTRAINT_PARAM
    #undef  CPP2_UFCS_CONSTRAINT_ARG
    #define CPP2_UFCS_CONSTRAINT_PARAM(MVFWD,QUALID,TEMPKW,...) , bool IsViable = \
   requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); } \
|| requires { MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(std::declval(), std::declval()...); }
    #define CPP2_UFCS_CONSTRAINT_ARG(...)                 IsViable
#endif

#define CPP2_UFCS_(LAMBDADEFCAPT,SFINAE,MVFWD,QUALID,TEMPKW,...) \
[LAMBDADEFCAPT]< \
    typename Obj, typename... Params \
    CPP2_UFCS_IS_NOTHROW_PARAM(MVFWD,QUALID,TEMPKW,__VA_ARGS__) \
    CPP2_UFCS_CONSTRAINT_PARAM(MVFWD,QUALID,TEMPKW,__VA_ARGS__) \
  > \
  CPP2_LAMBDA_NO_DISCARD (Obj&& obj, Params&& ...params) CPP2_FORCE_INLINE_LAMBDA_CLANG \
  noexcept(CPP2_UFCS_IS_NOTHROW_ARG(MVFWD,QUALID,TEMPKW,__VA_ARGS__)) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) \
    SFINAE( requires CPP2_UFCS_CONSTRAINT_ARG(MVFWD,QUALID,TEMPKW,__VA_ARGS__) ) \
  { \
    if constexpr      (requires{ CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); }) { \
        return                   CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
    } \
    else if constexpr (requires{ MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); }) { \
        return                   MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
    } \
    else if constexpr (requires{ obj.CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); }) { \
        static_assert( cpp2::impl::dependent_false::value, "error: implicit discard of an object's modified value is not allowed - this function call modifies 'obj', but 'obj' is never used again in the function so the new value is never used - if that's what you intended, add another line '_ = obj;' afterward to explicitly discard the new value of the object" ); \
        CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
        MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
    } \
    else if constexpr (requires{ MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(obj, CPP2_FORWARD(params)...); }) { \
        static_assert( cpp2::impl::dependent_false::value, "error: implicit discard of an object's modified value is not allowed - this function call modifies 'obj', but 'obj' is never used again in the function so the new value is never used - if that's what you intended, add another line '_ = obj;' afterward to explicitly discard the new value of the object" ); \
        CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
        MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
    } \
    else { \
        static_assert( cpp2::impl::dependent_false::value, "this function call syntax tries 'obj.func(...)', then 'func(obj,...);', but both failed - if this function call is passing a local variable that will be modified by the function, but that variable is never used again in the function so the new value is never used, that's likely the problem - if that's what you intended, add another line '_ = obj;' afterward to explicitly discard the new value of the object" ); \
        CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
        MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
    } \
  }

#define CPP2_UFCS(...)                                    CPP2_UFCS_(&,CPP2_UFCS_EMPTY,CPP2_UFCS_IDENTITY,(),,__VA_ARGS__)
#define CPP2_UFCS_MOVE(...)                               CPP2_UFCS_(&,CPP2_UFCS_EMPTY,std::move,(),,__VA_ARGS__)
#define CPP2_UFCS_FORWARD(...)                            CPP2_UFCS_(&,CPP2_UFCS_EMPTY,CPP2_FORWARD,(),,__VA_ARGS__)
#define CPP2_UFCS_TEMPLATE(...)                           CPP2_UFCS_(&,CPP2_UFCS_EMPTY,CPP2_UFCS_IDENTITY,(),template,__VA_ARGS__)
#define CPP2_UFCS_QUALIFIED_TEMPLATE(QUALID,...)          CPP2_UFCS_(&,CPP2_UFCS_EMPTY,CPP2_UFCS_IDENTITY,QUALID,template,__VA_ARGS__)
#define CPP2_UFCS_NONLOCAL(...)                           CPP2_UFCS_(,CPP2_UFCS_IDENTITY,CPP2_UFCS_IDENTITY,(),,__VA_ARGS__)
#define CPP2_UFCS_TEMPLATE_NONLOCAL(...)                  CPP2_UFCS_(,CPP2_UFCS_IDENTITY,CPP2_UFCS_IDENTITY,(),template,__VA_ARGS__)
#define CPP2_UFCS_QUALIFIED_TEMPLATE_NONLOCAL(QUALID,...) CPP2_UFCS_(,CPP2_UFCS_IDENTITY,CPP2_UFCS_IDENTITY,QUALID,template,__VA_ARGS__)


//  GCC 10 doesn't support 'requires' in forward declarations in some cases
//  Workaround: Disable the requires clause where that gets reasonable behavior
//  Diagnostic: static_assert the other cases that can't be worked around
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ == 10
    #define CPP2_REQUIRES(...) /* empty */
    #define CPP2_REQUIRES_(...) static_assert(false, "GCC 11 or higher is required to support variables and type-scope functions that have a 'requires' clause. This includes a type-scope 'forward' parameter of non-wildcard type, such as 'func: (this, forward s: std::string)', which relies on being able to add a 'requires' clause - in that case, use 'forward s: _' instead if you need the result to compile with GCC 10.")
#else
    #define CPP2_REQUIRES(...) requires (__VA_ARGS__)
    #define CPP2_REQUIRES_(...) requires (__VA_ARGS__)
#endif

//  Restore clang signed-to-unsigned conversion warnings
#ifdef __clang__
    #pragma clang diagnostic pop
#endif

#endif // CPP2_CPP2UTIL_H

Web Proxy Viewer  |  New URL  |  Original Page