GitHub Viewer
// Copyright (c) Herb Sutter
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//===========================================================================
// Cpp2 utilities:
// Language support implementations
// #include'd by generated Cpp1 code
//===========================================================================
#ifndef CPP2_UTIL_H
#define CPP2_UTIL_H
// If this implementation doesn't support source_location yet, disable it
#include
#if !defined(_MSC_VER) && !defined(__cpp_lib_source_location)
#undef CPP2_USE_SOURCE_LOCATION
#endif
// If the user requested making the entire C++ standard library available
// via module import (incl. via -pure-cpp2) or header include, do that
#if defined(CPP2_IMPORT_STD) || defined(CPP2_INCLUDE_STD)
// If C++23 'import std;' was requested but isn't available, fall back
// to the 'include std' path
#if defined(CPP2_IMPORT_STD) && defined(__cpp_lib_modules)
import std.compat;
// If 'include std' was requested, include all standard headers.
// This list tracks the current draft standard, so as of this
// writing includes draft C++26 headers like .
// Use a feature test #ifdef for each header that isn't supported
// by all of { VS 2022, g++-10, clang++-12 }
#else
#ifdef _MSC_VER
#include "intrin.h"
#endif
#include
#include
#include
#include
#ifdef __cpp_lib_barrier
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __cpp_lib_coroutine
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if __has_include()
#include
#endif
#include
#include
#ifdef __cpp_lib_debugging
#include
#endif
#include
#ifndef CPP2_NO_EXCEPTIONS
#include
#endif
// libstdc++ currently has a dependency on linking TBB if is
// included, and TBB seems to be not automatically installed and linkable
// on some GCC installations, so let's not pull in that little-used header
// in our -pure-cpp2 "import std;" simulation mode... if you need this,
// use mixed mode (not -pure-cpp2) and #include all the headers you need
// including this one
//
// #include
#ifdef __cpp_lib_expected
#include
#endif
#include
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
#include
#endif
#ifdef __cpp_lib_flat_map
#include
#endif
#ifdef __cpp_lib_flat_set
#include
#endif
#include
#include
#include
#include
#ifdef __cpp_lib_generator
#include
#endif
#ifdef __cpp_lib_hazard_pointer
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __cpp_lib_latch
#include
#endif
#include
#ifdef __cpp_lib_linalg
#include
#endif
#include
#include
#include
#ifdef __cpp_lib_mdspan
#include
#endif
#include
#ifdef __cpp_lib_memory_resource
#include
#endif
#include
#include
#include
#include
#include
#include
#ifdef __cpp_lib_print
#include
#endif
#include
#include
#include
#include
#ifdef __cpp_lib_rcu
#include
#endif
#include
#include
#ifdef __cpp_lib_semaphore
#include
#endif
#include
#include
#ifdef __cpp_lib_source_location
#include
#endif
#include
#ifdef __cpp_lib_spanstream
#include
#endif
#include
#include
#ifdef __cpp_lib_stacktrace
#include
#endif
#ifdef __cpp_lib_stdatomic_h
#include
#endif
#include
#if __has_include()
#if !defined(_MSC_VER) || _HAS_CXX23
#include
#endif
#endif
#ifdef __cpp_lib_jthread
#include
#endif
#include
#include
#include
#ifdef __cpp_lib_syncstream
#include
#endif
#include
#ifdef __cpp_lib_text_encoding
#include
#endif
#include
#include
#include
#include
#ifndef CPP2_NO_RTTI
#include
#endif
#include
#include
#include
#include
#include
#include
#endif
// Otherwise, just #include the facilities used in this header
#else
#ifdef _MSC_VER
#include "intrin.h"
#endif
#include
#include
#include
#include
#include
#include
#include
#ifndef CPP2_NO_EXCEPTIONS
#include
#endif
#ifdef __cpp_lib_expected
#include
#endif
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(CPP2_USE_SOURCE_LOCATION)
#include
#endif
#include
#include
#include
#include
#include
#include
#ifndef CPP2_NO_RTTI
#include
#endif
#include
#include
#include
#endif
#define CPP2_TYPEOF(x) std::remove_cvref_t
#if __cplusplus >= 202302L && \
( \
(defined(__clang_major__) && __clang_major__ >= 15) \
|| (defined(__GNUC__) && __GNUC__ >= 12) \
)
#define CPP2_COPY(x) auto(x)
#else
#define CPP2_COPY(x) CPP2_TYPEOF(x)(x)
#endif
#define CPP2_FORWARD(x) std::forward(x)
#define CPP2_PACK_EMPTY(x) (sizeof...(x) == 0)
#define CPP2_CONTINUE_BREAK(NAME) goto CONTINUE_##NAME; CONTINUE_##NAME: continue; goto BREAK_##NAME; BREAK_##NAME: break;
// these redundant goto's to avoid 'unused label' warnings
#if defined(_MSC_VER)
// MSVC can't handle 'inline constexpr' yet in all cases
#define CPP2_CONSTEXPR const
#else
#define CPP2_CONSTEXPR constexpr
#endif
namespace cpp2 {
//-----------------------------------------------------------------------
//
// Convenience names for fundamental types
//
// Note: De jure, some of these are optional per the C and C++ standards
// De facto, all of these are supported in all implementations I know of
//
//-----------------------------------------------------------------------
//
// Encouraged by default: Fixed-precision names
using i8 = std::int8_t ;
using i16 = std::int16_t ;
using i32 = std::int32_t ;
using i64 = std::int64_t ;
using u8 = std::uint8_t ;
using u16 = std::uint16_t ;
using u32 = std::uint32_t ;
using u64 = std::uint64_t ;
// Discouraged: Variable precision names
// short
using ushort = unsigned short;
// int
using uint = unsigned int;
// long
using ulong = unsigned long;
using longlong = long long;
using ulonglong = unsigned long long;
using longdouble = long double;
// Strongly discouraged, for compatibility/interop only
using _schar = signed char; // normally use i8 instead
using _uchar = unsigned char; // normally use u8 instead
//-----------------------------------------------------------------------
//
// General helpers
//
//-----------------------------------------------------------------------
//
inline constexpr auto max(auto... values) {
return std::max( { values... } );
}
template
inline constexpr auto is_any = std::disjunction_v;
template
struct aligned_storage {
alignas(Align) unsigned char data[Len];
};
template
requires requires { *std::declval(); }
using deref_t = decltype(*std::declval());
//-----------------------------------------------------------------------
//
// String: A helper workaround for passing a string literal as a
// template argument
//
//-----------------------------------------------------------------------
//
template
struct String
{
constexpr String(const char (&str)[N])
{
std::copy_n(str, N, value);
}
auto operator(String const&) const = default;
char value[N] = {};
};
//-----------------------------------------------------------------------
//
// contract_group
//
//-----------------------------------------------------------------------
//
#ifdef CPP2_USE_SOURCE_LOCATION
#define CPP2_SOURCE_LOCATION_PARAM , std::source_location where
#define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT , std::source_location where = std::source_location::current()
#define CPP2_SOURCE_LOCATION_PARAM_SOLO std::source_location where
#define CPP2_SOURCE_LOCATION_ARG , where
#else
#define CPP2_SOURCE_LOCATION_PARAM
#define CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT
#define CPP2_SOURCE_LOCATION_PARAM_SOLO
#define CPP2_SOURCE_LOCATION_ARG
#endif
// For C++23: make this std::string_view and drop the macro
// Before C++23 std::string_view was not guaranteed to be trivially copyable,
// and so in will pass it by const& and really it should be by value
#define CPP2_MESSAGE_PARAM char const*
#define CPP2_CONTRACT_MSG cpp2::message_to_cstr_adapter
inline auto message_to_cstr_adapter( CPP2_MESSAGE_PARAM msg ) -> CPP2_MESSAGE_PARAM { return msg ? msg : ""; }
inline auto message_to_cstr_adapter( std::string const& msg ) -> CPP2_MESSAGE_PARAM { return msg.c_str(); }
class contract_group {
public:
using handler = void (*)(CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM);
constexpr contract_group (handler h = {}) : reporter{h} { }
constexpr auto set_handler(handler h = {}) { reporter = h; }
constexpr auto get_handler() const -> handler { return reporter; }
constexpr auto has_handler() const -> bool { return reporter != handler{}; }
constexpr auto enforce(bool b, CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT)
-> void { if (!b) report_violation(msg CPP2_SOURCE_LOCATION_ARG); }
constexpr auto report_violation(CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT)
-> void { if (reporter) reporter(msg CPP2_SOURCE_LOCATION_ARG); }
private:
handler reporter;
};
[[noreturn]] inline auto report_and_terminate(std::string_view group, CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) noexcept -> void {
std::cerr
#ifdef CPP2_USE_SOURCE_LOCATION
std::shared_ptr {
// Prefer { } to ( ) as noted for unique.new
//
// Note this does mean we don't get the make_shared optimization a lot
// of the time -- we can restore that as soon as make_shared improves to
// allow list initialization. But the make_shared optimization isn't a
// huge deal anyway: it saves one allocation, but most of the cost of
// shared_ptrs is copying them and the allocation cost saving is probably
// outweighed by just a couple of shared_ptr copies; also, the make_shared
// optimization has the potential downside of keeping the raw storage
// alive longer when there are weak_ptrs. So, yes, we can and should
// restore the make_shared optimization as soon as make_shared supports
// list init, but I don't think it's all that important AFAIK
if constexpr (requires { T{CPP2_FORWARD(args)...}; }) {
// Why this calls 'unique.new': The workaround to use { } initialization
// requires calling naked 'new' to allocate the object separately anyway,
// so reuse the unique.new path that already does that (less code
// duplication, plus encapsulate the naked 'new' in one place)
return unique.cpp2_new(CPP2_FORWARD(args)...);
}
else {
return std::make_shared(CPP2_FORWARD(args)...);
}
}
} inline shared;
template
[[nodiscard]] auto cpp2_new(auto&& ...args) -> std::unique_ptr {
return unique.cpp2_new(CPP2_FORWARD(args)...);
}
//-----------------------------------------------------------------------
//
// in For "in" parameter
//
//-----------------------------------------------------------------------
//
template
constexpr bool prefer_pass_by_value =
sizeof(T) ;
//-----------------------------------------------------------------------
//
// Initialization: These are closely related...
//
// deferred_init For deferred-initialized local object
//
// out For out parameter
//
//-----------------------------------------------------------------------
//
template
class deferred_init {
alignas(T) std::byte data[sizeof(T)];
bool init = false;
auto t() -> T& { return *std::launder(reinterpret_cast(&data)); }
template
friend class out;
auto destroy() -> void { if (init) { t().~T(); } init = false; }
public:
deferred_init() noexcept { }
~deferred_init() noexcept { destroy(); }
auto value() noexcept -> T& { Default.enforce(init); return t(); }
auto construct(auto&& ...args) -> void { Default.enforce(!init); new (&data) T{CPP2_FORWARD(args)...}; init = true; }
};
template
class out {
// Not going to bother with std::variant here
union {
T* t;
deferred_init* dt;
};
out* ot = {};
bool has_t;
// Each out in a chain contains its own uncaught_count ...
int uncaught_count = Uncaught_exceptions();
// ... but all in a chain share the topmost called_construct_
bool called_construct_ = false;
public:
out(T* t_) noexcept : t{ t_}, has_t{true} { Default.enforce( t); }
out(deferred_init* dt_) noexcept : dt{dt_}, has_t{false} { Default.enforce(dt); }
out(out* ot_) noexcept : ot{ot_}, has_t{ot_->has_t} { Default.enforce(ot);
if (has_t) { t = ot->t; }
else { dt = ot->dt; }
}
auto called_construct() -> bool& {
if (ot) { return ot->called_construct(); }
else { return called_construct_; }
}
// In the case of an exception, if the parameter was uninitialized
// then leave it in the same state on exit (strong guarantee)
~out() {
if (called_construct() && uncaught_count != Uncaught_exceptions()) {
Default.enforce(!has_t);
dt->destroy();
called_construct() = false;
}
}
auto construct(auto&& ...args) -> void {
if (has_t || called_construct()) {
if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) {
Default.enforce( t );
*t = T(CPP2_FORWARD(args)...);
}
else {
Default.report_violation("attempted to copy assign, but copy assignment is not available");
}
}
else {
Default.enforce( dt );
if (dt->init) {
if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) {
dt->value() = T(CPP2_FORWARD(args)...);
}
else {
Default.report_violation("attempted to copy assign, but copy assignment is not available");
}
}
else {
dt->construct(CPP2_FORWARD(args)...);
called_construct() = true;
}
}
}
auto value() noexcept -> T& {
if (has_t) {
Default.enforce( t );
return *t;
}
else {
Default.enforce( dt );
return dt->value();
}
}
};
//-----------------------------------------------------------------------
//
// CPP2_UFCS: Variadic macro generating a variadic lamba, oh my...
//
//-----------------------------------------------------------------------
//
// Workaround .
#define CPP2_FORCE_INLINE_LAMBDA_CLANG /* empty */
#if defined(_MSC_VER) && !defined(__clang_major__)
#define CPP2_FORCE_INLINE __forceinline
#define CPP2_FORCE_INLINE_LAMBDA [[msvc::forceinline]]
#define CPP2_LAMBDA_NO_DISCARD
#else
#define CPP2_FORCE_INLINE __attribute__((always_inline))
#if defined(__clang__)
#define CPP2_FORCE_INLINE_LAMBDA /* empty */
#undef CPP2_FORCE_INLINE_LAMBDA_CLANG
#define CPP2_FORCE_INLINE_LAMBDA_CLANG __attribute__((always_inline))
#else
#define CPP2_FORCE_INLINE_LAMBDA __attribute__((always_inline))
#endif
#if defined(__clang_major__)
// Also check __cplusplus, only to satisfy Clang -pedantic-errors
#if __cplusplus >= 202302L && (__clang_major__ > 13 || (__clang_major__ == 13 && __clang_minor__ >= 2))
#define CPP2_LAMBDA_NO_DISCARD [[nodiscard]]
#else
#define CPP2_LAMBDA_NO_DISCARD
#endif
#elif defined(__GNUC__)
#if __GNUC__ >= 9
#define CPP2_LAMBDA_NO_DISCARD [[nodiscard]]
#else
#define CPP2_LAMBDA_NO_DISCARD
#endif
#if ((__GNUC__ * 100) + __GNUC_MINOR__) < 1003
// GCC 10.2 doesn't support this feature (10.3 is fine)
#undef CPP2_FORCE_INLINE_LAMBDA
#define CPP2_FORCE_INLINE_LAMBDA
#endif
#else
#define CPP2_LAMBDA_NO_DISCARD
#endif
#endif
#define CPP2_UFCS_IDENTITY(...) __VA_ARGS__
#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__
// Ideally, the expression `CPP2_UFCS_IS_NOTHROW` expands to
// is in the _noexcept-specifier_ of the UFCS lambda, but without 'std::declval'.
// To workaround [GCC bug 101043](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101043),
// we instead make it a template parameter of the UFCS lambda.
// But using a template parameter, Clang also ICEs on an application.
// So we use these `NOTHROW` macros to fall back to the ideal for when not using GCC.
#define CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,...) \
requires { requires requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); }; \
requires noexcept(std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...)); } \
|| requires { requires !requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); }; \
requires noexcept(MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(std::declval(), std::declval()...)); }
#define CPP2_UFCS_IS_NOTHROW_PARAM(...) /*empty*/
#define CPP2_UFCS_IS_NOTHROW_ARG(MVFWD,QUALID,TEMPKW,...) CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,__VA_ARGS__)
#if defined(__GNUC__) && !defined(__clang__)
#undef CPP2_UFCS_IS_NOTHROW_PARAM
#undef CPP2_UFCS_IS_NOTHROW_ARG
#define CPP2_UFCS_IS_NOTHROW_PARAM(MVFWD,QUALID,TEMPKW,...) , bool IsNothrow = CPP2_UFCS_IS_NOTHROW(MVFWD,QUALID,TEMPKW,__VA_ARGS__)
#define CPP2_UFCS_IS_NOTHROW_ARG(...) IsNothrow
#if __GNUC__ < 11
#undef CPP2_UFCS_IS_NOTHROW_PARAM
#undef CPP2_UFCS_IS_NOTHROW_ARG
#define CPP2_UFCS_IS_NOTHROW_PARAM(...) /*empty*/
#define CPP2_UFCS_IS_NOTHROW_ARG(...) false // GCC 10 UFCS is always potentially-throwing.
#endif
#endif
// Ideally, the expression `CPP2_UFCS_CONSTRAINT_ARG` expands to
// is in the _requires-clause_ of the UFCS lambda.
// To workaround an MSVC bug within a member function 'F' where UFCS is also for 'F'
// (),
// we instead make it a template parameter of the UFCS lambda.
// But using a template parameter, Clang also ICEs and GCC rejects a local 'F'.
// Also, Clang rejects the SFINAE test case when using 'std::declval'.
// So we use these `CONSTRAINT` macros to fall back to the ideal for when not using MSVC.
#define CPP2_UFCS_CONSTRAINT_PARAM(...) /*empty*/
#define CPP2_UFCS_CONSTRAINT_ARG(MVFWD,QUALID,TEMPKW,...) \
requires { CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); } \
|| requires { MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); }
#if defined(_MSC_VER)
#undef CPP2_UFCS_CONSTRAINT_PARAM
#undef CPP2_UFCS_CONSTRAINT_ARG
#define CPP2_UFCS_CONSTRAINT_PARAM(MVFWD,QUALID,TEMPKW,...) , bool IsViable = \
requires { std::declval().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval()...); } \
|| requires { MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(std::declval(), std::declval()...); }
#define CPP2_UFCS_CONSTRAINT_ARG(...) IsViable
#endif
#define CPP2_UFCS_(LAMBDADEFCAPT,MVFWD,QUALID,TEMPKW,...) \
[LAMBDADEFCAPT]< \
typename Obj, typename... Params \
CPP2_UFCS_IS_NOTHROW_PARAM(MVFWD,QUALID,TEMPKW,__VA_ARGS__) \
CPP2_UFCS_CONSTRAINT_PARAM(MVFWD,QUALID,TEMPKW,__VA_ARGS__) \
> \
CPP2_LAMBDA_NO_DISCARD (Obj&& obj, Params&& ...params) CPP2_FORCE_INLINE_LAMBDA_CLANG \
noexcept(CPP2_UFCS_IS_NOTHROW_ARG(MVFWD,QUALID,TEMPKW,__VA_ARGS__)) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) \
requires CPP2_UFCS_CONSTRAINT_ARG(MVFWD,QUALID,TEMPKW,__VA_ARGS__) { \
if constexpr (requires{ CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
} else { \
return MVFWD(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__)(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}
#define CPP2_UFCS(...) CPP2_UFCS_(&,CPP2_UFCS_IDENTITY,(),,__VA_ARGS__)
#define CPP2_UFCS_MOVE(...) CPP2_UFCS_(&,std::move,(),,__VA_ARGS__)
#define CPP2_UFCS_FORWARD(...) CPP2_UFCS_(&,CPP2_FORWARD,(),,__VA_ARGS__)
#define CPP2_UFCS_TEMPLATE(...) CPP2_UFCS_(&,CPP2_UFCS_IDENTITY,(),template,__VA_ARGS__)
#define CPP2_UFCS_QUALIFIED_TEMPLATE(QUALID,...) CPP2_UFCS_(&,CPP2_UFCS_IDENTITY,QUALID,template,__VA_ARGS__)
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,CPP2_UFCS_IDENTITY,(),,__VA_ARGS__)
#define CPP2_UFCS_TEMPLATE_NONLOCAL(...) CPP2_UFCS_(,CPP2_UFCS_IDENTITY,(),template,__VA_ARGS__)
#define CPP2_UFCS_QUALIFIED_TEMPLATE_NONLOCAL(QUALID,...) CPP2_UFCS_(,CPP2_UFCS_IDENTITY,QUALID,template,__VA_ARGS__)
//-----------------------------------------------------------------------
//
// to_string for string interpolation
//
//-----------------------------------------------------------------------
//
// For use when returning "no such thing", such as
// when customizing "as" for std::variant
struct nonesuch_ {
auto operator==(auto const&) -> bool { return false; }
};
constexpr inline nonesuch_ nonesuch;
inline auto to_string(...) -> std::string
{
return "(customize me - no cpp2::to_string overload exists for this type)";
}
inline auto to_string(nonesuch_) -> std::string
{
return "(invalid type)";
}
inline auto to_string(std::same_as auto const&) -> std::string
{
return "std::any";
}
inline auto to_string(bool b) -> std::string
{
return b ? "true" : "false";
}
template
inline auto to_string(T const& t) -> std::string
requires requires { std::to_string(t); }
{
return std::to_string(t);
}
inline auto to_string(char const& t) -> std::string
{
return std::string{t};
}
inline auto to_string(char const* s) -> std::string
{
return std::string{s};
}
inline auto to_string(std::string const& s) -> std::string const&
{
return s;
}
template
inline auto to_string(T const& sv) -> std::string
requires (std::is_convertible_v
&& !std::is_convertible_v)
{
return std::string{sv};
}
template
inline auto to_string(std::variant const& v) -> std::string;
template < typename T, typename U>
inline auto to_string(std::pair const& p) -> std::string;
template < typename... Ts>
inline auto to_string(std::tuple const& t) -> std::string;
template
inline auto to_string(std::optional const& o) -> std::string {
if (o.has_value()) {
return cpp2::to_string(o.value());
}
return "(empty)";
}
template
inline auto to_string(std::variant const& v) -> std::string
{
if (v.valueless_by_exception()) return "(empty)";
// Need to guard this with is_any otherwise the get_if is illegal
if constexpr (is_any) if (std::get_if(&v) != nullptr) return "(empty)";
return std::visit([](auto&& arg) -> std::string {
return cpp2::to_string(arg);
}, v);
}
template < typename T, typename U>
inline auto to_string(std::pair const& p) -> std::string
{
return "(" + cpp2::to_string(p.first) + ", " + cpp2::to_string(p.second) + ")";
}
template < typename... Ts>
inline auto to_string(std::tuple const& t) -> std::string
{
if constexpr (sizeof...(Ts) == 0) {
return "()";
} else {
std::string out = "(" + cpp2::to_string(std::get(t));
std::apply([&out](auto&&, auto&&... args) {
((out += ", " + cpp2::to_string(args)), ...);
}, t);
out += ")";
return out;
}
}
// MSVC supports it but doesn't define __cpp_lib_format until the ABI stablizes, but here
// don't care about that, so consider it as supported since VS 2019 16.10 (_MSC_VER 1929)
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
inline auto to_string(auto&& value, std::string_view fmt) -> std::string
{
return std::vformat(fmt, std::make_format_args(CPP2_FORWARD(value)));
}
#else
inline auto to_string(auto&& value, std::string_view) -> std::string
{
// This Cpp1 implementation does not support -ted string interpolation
// so the best we can do is ignore the formatting request (degraded operation
// seems better than a dynamic error message string or a hard error)
return to_string(CPP2_FORWARD(value));
}
#endif
//-----------------------------------------------------------------------
//
// is and as
//
//-----------------------------------------------------------------------
//
//-------------------------------------------------------------------------------------------------------------
// Built-in is
//
// For designating "holds no value" -- used only with is, not as
// TODO: Does this really warrant a new synonym? Perhaps "is void" is enough
using empty = void;
// Templates
//
template
constexpr auto is(C< Ts...> const& ) -> bool {
return true;
}
#if defined(_MSC_VER)
template
constexpr auto is( T const& ) -> bool {
return false;
}
#else
template
constexpr auto is( T const& ) -> bool {
return false;
}
#endif
template
constexpr auto is( C const& ) -> bool {
return true;
}
template
constexpr auto is( T const& ) -> bool {
return false;
}
// Types
//
template< typename C, typename X >
auto is( X const& x ) -> bool {
if constexpr (
std::is_same_v
|| std::is_base_of_v
)
{
return true;
}
else if constexpr (
std::is_base_of_v
|| (
std::is_polymorphic_v
&& std::is_polymorphic_v
)
)
{
if constexpr (std::is_pointer_v) {
return Dynamic_cast(x) != nullptr;
}
else {
return Dynamic_cast(&x) != nullptr;
}
}
else if constexpr (
requires { *x; X(); }
&& std::is_same_v
)
{
return x == X();
}
else {
return false;
}
}
// Values
//
inline constexpr auto is( auto const& x, auto&& value ) -> bool
{
// Value with customized operator_is case
if constexpr (requires{ x.op_is(value); }) {
return x.op_is(value);
}
// Predicate case
else if constexpr (requires{ bool{ value(x) }; }) {
return value(x);
}
else if constexpr (std::is_function_v || requires{ &value.operator(); }) {
return false;
}
// Value equality case
else if constexpr (requires{ bool{x == value}; }) {
return x == value;
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// Built-in as
//
// The 'as' cast functions are so use that order here
// If it's confusing, we can switch this to
template< typename To, typename From >
inline constexpr auto is_narrowing_v =
// [dcl.init.list] 7.1
(std::is_floating_point_v && std::is_integral_v) ||
// [dcl.init.list] 7.2
(std::is_floating_point_v && std::is_floating_point_v && sizeof(From) > sizeof(To)) ||
// [dcl.init.list] 7.3
(std::is_integral_v && std::is_floating_point_v) ||
(std::is_enum_v && std::is_floating_point_v) ||
// [dcl.init.list] 7.4
(std::is_integral_v && std::is_integral_v && sizeof(From) > sizeof(To)) ||
(std::is_enum_v && std::is_integral_v && sizeof(From) > sizeof(To)) ||
// [dcl.init.list] 7.5
(std::is_pointer_v && std::is_same_v);
template
inline constexpr auto program_violates_type_safety_guarantee = sizeof...(Ts) < 0;
// For literals we can check for safe 'narrowing' at a compile time (e.g., 1 as std::size_t)
template< typename C, auto x >
inline constexpr bool is_castable_v =
std::is_integral_v &&
std::is_integral_v &&
!(static_cast(static_cast(x)) != x ||
(
(std::is_signed_v != std::is_signed_v) &&
((static_cast(x) < C{}) != (x < CPP2_TYPEOF(x){}))
)
);
// As
//
template< typename C, auto x >
requires (std::is_arithmetic_v && std::is_arithmetic_v)
inline constexpr auto as() -> auto
{
if constexpr ( is_castable_v ) {
return static_cast(x);
} else {
return nonesuch;
}
}
template< typename C, typename X >
auto as(X const& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) {
if constexpr (
std::is_floating_point_v &&
std::is_floating_point_v &&
sizeof(CPP2_TYPEOF(x)) > sizeof(C)
)
{
return CPP2_COPY(nonesuch);
}
// Signed/unsigned conversions to a not-smaller type are handled as a precondition,
// and trying to cast from a value that is in the half of the value space that isn't
// representable in the target type C is flagged as a Type safety contract violation
else if constexpr (
std::is_integral_v &&
std::is_integral_v &&
std::is_signed_v != std::is_signed_v &&
sizeof(CPP2_TYPEOF(x)) not lossy
static_cast(c) == x && (c < C{}) == (x < CPP2_TYPEOF(x){}),
"dynamic lossy narrowing conversion attempt detected" CPP2_SOURCE_LOCATION_ARG
);
return CPP2_COPY(c);
}
else if constexpr (std::is_same_v && std::is_integral_v) {
return cpp2::to_string(x);
}
else if constexpr (std::is_same_v) {
return x;
}
else if constexpr (std::is_base_of_v) {
return static_cast(x);
}
else if constexpr (std::is_base_of_v) {
return Dynamic_cast(x);
}
else if constexpr (
std::is_pointer_v
&& std::is_pointer_v
&& requires { requires std::is_base_of_v; }
)
{
return Dynamic_cast(x);
}
else if constexpr (requires { C{x}; }) {
// Experiment: Recognize the nested `::value_type` pattern for some dynamic library types
// like std::optional, and try to prevent accidental narrowing conversions even when
// those types themselves don't defend against them
if constexpr( requires { requires std::is_convertible_v; } ) {
if constexpr( is_narrowing_v) {
return nonesuch;
}
}
return C{x};
}
else {
return nonesuch;
}
}
template< typename C, typename X >
auto as( X& x ) -> decltype(auto) {
if constexpr (std::is_same_v) {
return x;
}
else if constexpr (std::is_base_of_v) {
return static_cast(x);
}
else if constexpr (std::is_base_of_v) {
return Dynamic_cast(x);
}
else {
return as(std::as_const(x));
}
}
//-------------------------------------------------------------------------------------------------------------
// std::variant is and as
//
// Common internal helper
//
template
constexpr auto operator_as( std::variant && x ) -> decltype(auto) {
if constexpr (I < std::variant_size_v) {
return std::get( x );
}
else {
return nonesuch;
}
}
template
constexpr auto operator_as( std::variant & x ) -> decltype(auto) {
if constexpr (I < std::variant_size_v) {
return std::get( x );
}
else {
return nonesuch;
}
}
template
constexpr auto operator_as( std::variant const& x ) -> decltype(auto) {
if constexpr (I < std::variant_size_v) {
return std::get( x );
}
else {
return nonesuch;
}
}
// is Type
//
template
constexpr auto operator_is( std::variant const& x ) {
return x.index();
}
template
auto is( std::variant const& x );
// is Value
//
template
constexpr auto is( std::variant const& x, auto&& value ) -> bool
{
// Predicate case
if constexpr (requires{ bool{ value(operator_as< 0>(x)) }; }) { if (x.index() == 0) return value(operator_as< 0>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 1>(x)) }; }) { if (x.index() == 1) return value(operator_as< 1>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 2>(x)) }; }) { if (x.index() == 2) return value(operator_as< 2>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 3>(x)) }; }) { if (x.index() == 3) return value(operator_as< 3>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 4>(x)) }; }) { if (x.index() == 4) return value(operator_as< 4>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 5>(x)) }; }) { if (x.index() == 5) return value(operator_as< 5>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 6>(x)) }; }) { if (x.index() == 6) return value(operator_as< 6>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 7>(x)) }; }) { if (x.index() == 7) return value(operator_as< 7>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 8>(x)) }; }) { if (x.index() == 8) return value(operator_as< 8>(x)); }
else if constexpr (requires{ bool{ value(operator_as< 9>(x)) }; }) { if (x.index() == 9) return value(operator_as< 9>(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 10) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 11) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 12) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 13) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 14) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 15) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 16) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 17) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 18) return value(operator_as(x)); }
else if constexpr (requires{ bool{ value(operator_as(x)) }; }) { if (x.index() == 19) return value(operator_as(x)); }
else if constexpr (std::is_function_v || requires{ &value.operator(); }) {
return false;
}
// Value case
else {
if constexpr (requires{ bool{ operator_as< 0>(x) == value }; }) { if (x.index() == 0) return operator_as< 0>(x) == value; }
if constexpr (requires{ bool{ operator_as< 1>(x) == value }; }) { if (x.index() == 1) return operator_as< 1>(x) == value; }
if constexpr (requires{ bool{ operator_as< 2>(x) == value }; }) { if (x.index() == 2) return operator_as< 2>(x) == value; }
if constexpr (requires{ bool{ operator_as< 3>(x) == value }; }) { if (x.index() == 3) return operator_as< 3>(x) == value; }
if constexpr (requires{ bool{ operator_as< 4>(x) == value }; }) { if (x.index() == 4) return operator_as< 4>(x) == value; }
if constexpr (requires{ bool{ operator_as< 5>(x) == value }; }) { if (x.index() == 5) return operator_as< 5>(x) == value; }
if constexpr (requires{ bool{ operator_as< 6>(x) == value }; }) { if (x.index() == 6) return operator_as< 6>(x) == value; }
if constexpr (requires{ bool{ operator_as< 7>(x) == value }; }) { if (x.index() == 7) return operator_as< 7>(x) == value; }
if constexpr (requires{ bool{ operator_as< 8>(x) == value }; }) { if (x.index() == 8) return operator_as< 8>(x) == value; }
if constexpr (requires{ bool{ operator_as< 9>(x) == value }; }) { if (x.index() == 9) return operator_as< 9>(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 10) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 11) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 12) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 13) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 14) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 15) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 16) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 17) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 18) return operator_as(x) == value; }
if constexpr (requires{ bool{ operator_as(x) == value }; }) { if (x.index() == 19) return operator_as(x) == value; }
}
return false;
}
// as
//
template
auto is( std::variant const& x ) {
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 0>(x)), T >) { if (x.index() == 0) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 1>(x)), T >) { if (x.index() == 1) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 2>(x)), T >) { if (x.index() == 2) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 4>(x)), T >) { if (x.index() == 4) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 5>(x)), T >) { if (x.index() == 5) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 6>(x)), T >) { if (x.index() == 6) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 7>(x)), T >) { if (x.index() == 7) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 8>(x)), T >) { if (x.index() == 8) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 9>(x)), T >) { if (x.index() == 9) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 10) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 11) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 12) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 13) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 14) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 15) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 16) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 17) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 18) return true; }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 19) return true; }
if constexpr (std::is_same_v< T, empty > ) {
if (x.valueless_by_exception()) return true;
// Need to guard this with is_any otherwise the get_if is illegal
if constexpr (is_any) return std::get_if(&x) != nullptr;
}
return false;
}
template
auto as( std::variant && x ) -> decltype(auto) {
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 0>(x)), T >) { if (x.index() == 0) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 1>(x)), T >) { if (x.index() == 1) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 2>(x)), T >) { if (x.index() == 2) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 4>(x)), T >) { if (x.index() == 4) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 5>(x)), T >) { if (x.index() == 5) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 6>(x)), T >) { if (x.index() == 6) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 7>(x)), T >) { if (x.index() == 7) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 8>(x)), T >) { if (x.index() == 8) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 9>(x)), T >) { if (x.index() == 9) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 10) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 11) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 12) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 13) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 14) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 15) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 16) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 17) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 18) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 19) return operator_as(x); }
Throw( std::bad_variant_access(), "'as' cast failed for 'variant'");
}
template
auto as( std::variant & x ) -> decltype(auto) {
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 0>(x)), T >) { if (x.index() == 0) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 1>(x)), T >) { if (x.index() == 1) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 2>(x)), T >) { if (x.index() == 2) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 4>(x)), T >) { if (x.index() == 4) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 5>(x)), T >) { if (x.index() == 5) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 6>(x)), T >) { if (x.index() == 6) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 7>(x)), T >) { if (x.index() == 7) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 8>(x)), T >) { if (x.index() == 8) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 9>(x)), T >) { if (x.index() == 9) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 10) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 11) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 12) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 13) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 14) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 15) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 16) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 17) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 18) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 19) return operator_as(x); }
Throw( std::bad_variant_access(), "'as' cast failed for 'variant'");
}
template
auto as( std::variant const& x ) -> decltype(auto) {
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 0>(x)), T >) { if (x.index() == 0) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 1>(x)), T >) { if (x.index() == 1) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 2>(x)), T >) { if (x.index() == 2) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 3>(x)), T >) { if (x.index() == 3) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 4>(x)), T >) { if (x.index() == 4) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 5>(x)), T >) { if (x.index() == 5) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 6>(x)), T >) { if (x.index() == 6) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 7>(x)), T >) { if (x.index() == 7) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 8>(x)), T >) { if (x.index() == 8) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as< 9>(x)), T >) { if (x.index() == 9) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 10) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 11) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 12) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 13) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 14) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 15) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 16) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 17) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 18) return operator_as(x); }
if constexpr (std::is_same_v< CPP2_TYPEOF(operator_as(x)), T >) { if (x.index() == 19) return operator_as(x); }
Throw( std::bad_variant_access(), "'as' cast failed for 'variant'");
}
//-------------------------------------------------------------------------------------------------------------
// std::any is and as
//
// is Type
//
template
requires (std::is_same_v && !std::is_same_v && !std::is_same_v)
constexpr auto is( X const& x ) -> bool
{ return x.type() == Typeid(); }
template
requires (std::is_same_v && std::is_same_v)
constexpr auto is( X const& x ) -> bool
{ return !x.has_value(); }
// is Value
//
inline constexpr auto is( std::any const& x, auto&& value ) -> bool
{
// Predicate case
if constexpr (requires{ bool{ value(x) }; }) {
return value(x);
}
else if constexpr (std::is_function_v || requires{ &value.operator(); }) {
return false;
}
// Value case
else if constexpr (requires{ bool{ *std::any_cast(&x) == value }; }) {
auto pvalue = std::any_cast(&x);
return pvalue && *pvalue == value;
}
// else
return false;
}
// as
//
template
requires (!std::is_reference_v && std::is_same_v && !std::is_same_v)
constexpr auto as( X const& x ) -> T
{ return std::any_cast( x ); }
//-------------------------------------------------------------------------------------------------------------
// std::optional is and as
//
// is Type
//
template
requires std::is_same_v
constexpr auto is( X const& x ) -> bool
{ return x.has_value(); }
template
requires std::is_same_v
constexpr auto is( std::optional const& x ) -> bool
{ return !x.has_value(); }
// is Value
//
template
constexpr auto is( std::optional const& x, auto&& value ) -> bool
{
// Predicate case
if constexpr (requires{ bool{ value(x) }; }) {
return value(x);
}
else if constexpr (std::is_function_v || requires{ &value.operator(); }) {
return false;
}
// Value case
else if constexpr (requires{ bool{ x.value() == value }; }) {
return x.has_value() && x.value() == value;
}
return false;
}
// as
//
template
requires std::is_same_v
constexpr auto as( X const& x ) -> decltype(auto)
{ return x.value(); }
//-----------------------------------------------------------------------
//
// A variation of GSL's final_action_success / finally
//
// finally ensures something is run at the end of a scope always
//
// finally_success ensures something is run at the end of a scope
// if no exception is thrown
//
// finally_presuccess ensures a group of add'd operations are run
// immediately before (not after) the return if no exception is
// thrown - right now this is used only for postconditions, so
// they can inspect named return values before they're moved from
//
//-----------------------------------------------------------------------
//
template
class finally_success
{
public:
explicit finally_success(const F& ff) noexcept : f{ff} { }
explicit finally_success(F&& ff) noexcept : f{std::move(ff)} { }
~finally_success() noexcept
{
if (invoke && ecount == std::uncaught_exceptions()) {
f();
}
}
finally_success(finally_success&& that) noexcept
: f(std::move(that.f)), invoke(std::exchange(that.invoke, false))
{ }
finally_success(finally_success const&) = delete;
void operator= (finally_success const&) = delete;
void operator= (finally_success&&) = delete;
private:
F f;
int ecount = std::uncaught_exceptions();
bool invoke = true;
};
template
class finally
{
public:
explicit finally(const F& ff) noexcept : f{ff} { }
explicit finally(F&& ff) noexcept : f{std::move(ff)} { }
~finally() noexcept { f(); }
finally(finally&& that) noexcept
: f(std::move(that.f)), invoke(std::exchange(that.invoke, false))
{ }
finally (finally const&) = delete;
void operator=(finally const&) = delete;
void operator=(finally&&) = delete;
private:
F f;
bool invoke = true;
};
class finally_presuccess
{
public:
finally_presuccess() = default;
auto add(const auto& f) { fs.push_back(f); }
// In compiled Cpp2 code, this function will be called
// immediately before 'return' (both explicit and implicit)
auto run() {
if (invoke && ecount == std::uncaught_exceptions()) {
for (auto const& f : fs) {
f();
}
}
invoke = false;
}
~finally_presuccess() noexcept {
run();
}
finally_presuccess(finally_presuccess const&) = delete;
void operator= (finally_presuccess const&) = delete;
void operator= (finally_presuccess &&) = delete;
private:
std::vector fs;
int ecount = std::uncaught_exceptions();
bool invoke = true;
};
//-----------------------------------------------------------------------
//
// args: see main() arguments as vector
//
//-----------------------------------------------------------------------
//
struct args_t : std::vector
{
args_t(int c, char** v) : vector{static_cast(c)}, argc{c}, argv{v} {}
mutable int argc = 0; // mutable for compatibility with frameworks that take 'int& argc'
char** argv = nullptr;
};
inline auto make_args(int argc, char** argv) -> args_t
{
auto ret = args_t{argc, argv};
auto args = std::span(argv, static_cast(argc));
std::copy( args.begin(), args.end(), ret.data());
return ret;
}
//-----------------------------------------------------------------------
//
// alien_memory: memory typed as T but that is outside C++ and that the
// compiler may not assume it knows anything at all about
//
//-----------------------------------------------------------------------
//
template
using alien_memory = T volatile;
//-----------------------------------------------------------------------
//
// An implementation of GSL's narrow_cast with a clearly 'unsafe' name
//
//-----------------------------------------------------------------------
//
template
constexpr auto unsafe_narrow( X&& x ) noexcept -> decltype(auto)
{
return static_cast(CPP2_FORWARD(x));
}
//-----------------------------------------------------------------------
//
// has_flags: query whether a flag_enum value has all flags in 'flags' set
//
// flags set of flags to check
//
// Returns a function object that takes a 'value' of the same type as
// 'flags', and evaluates to true if and only if 'value' has set all of
// the bits set in 'flags'
//
//-----------------------------------------------------------------------
//
template
auto has_flags(T flags)
{
return [=](T value) { return (value & flags) == flags; };
}
//-----------------------------------------------------------------------
//
// Speculative: RAII wrapping for the C standard library
//
// As part of embracing compatibility while also reducing what we have to
// teach and learn about C++ (which includes the C standard library), I
// was curious to see if we can improve use of the C standard library
// from Cpp2 code... UFCS is a part of that, and then RAII destructors is
// another that goes hand in hand with that, hence this section...
// but see caveat note at the end.
//
//-----------------------------------------------------------------------
//
template
class c_raii {
T t;
D dtor;
public:
c_raii( T t_, D d )
: t{ t_ }
, dtor{ d }
{ }
~c_raii() { dtor(t); }
operator T&() { return t; }
c_raii(c_raii const&) = delete;
auto operator=(c_raii const&) = delete;
};
inline auto fopen( const char* filename, const char* mode ) {
// Suppress annoying deprecation warning about fopen
#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4996 )
#endif
auto x = std::fopen(filename, mode);
#ifdef _MSC_VER
#pragma warning( pop )
#endif
if (!x) {
Throw( std::make_error_condition(std::errc::no_such_file_or_directory), "'fopen' attempt failed");
}
return c_raii( x, &std::fclose );
}
// Caveat: There's little else in the C stdlib that allocates a resource...
//
// malloc is already wrapped like this via std::unique_ptr, which
// typically uses malloc or gets memory from the same pool
// thrd_create std::jthread is better
//
// ... is that it? I don't think it's useful to provide a c_raii just for fopen,
// but perhaps c_raii may be useful for bringing forward third-party C code too,
// with cpp2::fopen as a starting example.
//-----------------------------------------------------------------------
//
// Signed/unsigned comparison checks
//
//-----------------------------------------------------------------------
//
template
CPP2_FORCE_INLINE constexpr auto cmp_mixed_signedness_check() -> void
{
if constexpr (
std::is_same_v ||
std::is_same_v
)
{
static_assert(
program_violates_type_safety_guarantee,
"comparing bool values using < = > is unsafe and not allowed - are you missing parentheses?");
}
else if constexpr (
std::is_integral_v &&
std::is_integral_v &&
std::is_signed_v != std::is_signed_v
)
{
// Note: It's tempting here to "just call std::cmp_*() instead"
// which does signed/unsigned relational comparison correctly
// for negative values, and so silently "fix that for you." But
// doing that has security pitfalls for the reasons described at
// https://github.com/hsutter/cppfront/issues/220, so this
// static_assert to reject the comparison is the right way to go.
static_assert(
program_violates_type_safety_guarantee,
"mixed signed/unsigned comparison is unsafe - prefer using .ssize() instead of .size(), consider using std::cmp_less instead, or consider explicitly casting one of the values to change signedness by using 'as' or 'cpp2::unsafe_narrow'"
);
}
}
CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u) -> decltype(auto)
requires requires {CPP2_FORWARD(t) < CPP2_FORWARD(u);}
{
cmp_mixed_signedness_check();
return CPP2_FORWARD(t) < CPP2_FORWARD(u);
}
CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u) -> decltype(auto)
{
static_assert(
program_violates_type_safety_guarantee,
"attempted to compare '' for incompatible types"
);
return nonesuch;
}
CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u) -> decltype(auto)
requires requires {CPP2_FORWARD(t) >= CPP2_FORWARD(u);}
{
cmp_mixed_signedness_check();
return CPP2_FORWARD(t) >= CPP2_FORWARD(u);
}
CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u) -> decltype(auto)
{
static_assert(
program_violates_type_safety_guarantee,
"attempted to compare '>=' for incompatible types"
);
return nonesuch;
}
//-----------------------------------------------------------------------
//
// A static-asserting "as" for better diagnostics than raw 'nonesuch'
//
// Note for the future: This needs go after all 'as', which is fine for
// the ones in this file but will have problems with further user-
// defined 'as' customizations. One solution would be to make the main
// 'as' be a class template, and have all customizations be actual
// specializations... that way name lookup should find the primary
// template first and then see later specializations. Or we could just
// remove this and live with the 'nonesuch' error messages. Either way,
// we don't need anything more right now, this solution is fine to
// unblock general progress
//
//-----------------------------------------------------------------------
//
template< typename C >
inline constexpr auto as_( auto&& x ) -> decltype(auto)
{
if constexpr (is_narrowing_v) {
static_assert(
program_violates_type_safety_guarantee,
"'as' does not allow unsafe narrowing conversions - if you're sure you want this, use `unsafe_narrow()` to force the conversion"
);
}
else if constexpr( std::is_same_v< CPP2_TYPEOF(as(CPP2_FORWARD(x))), nonesuch_ > ) {
static_assert(
program_violates_type_safety_guarantee,
"No safe 'as' cast available - please check your cast"
);
}
// else
return as(CPP2_FORWARD(x));
}
template< typename C, auto x >
inline constexpr auto as_() -> decltype(auto)
{
if constexpr (requires { as(); }) {
if constexpr( std::is_same_v< CPP2_TYPEOF((as())), nonesuch_ > ) {
static_assert(
program_violates_type_safety_guarantee,
"Literal cannot be narrowed using 'as' - if you're sure you want this, use 'unsafe_narrow()' to force the conversion"
);
}
}
else {
static_assert(
program_violates_type_safety_guarantee,
"No safe 'as' cast available - please check your cast"
);
}
// else
return as();
}
}
using cpp2::cpp2_new;
// Stabilize line numbers for "compatibility" static assertions that we know
// will fire for some compilers, to keep regression test outputs cleaner
#line 9999
// GCC 10 doesn't support 'requires' in forward declarations in some cases
// Workaround: Disable the requires clause where that gets reasonable behavior
// Diagnostic: static_assert the other cases that can't be worked around
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ == 10
#define CPP2_REQUIRES(...) /* empty */
#define CPP2_REQUIRES_(...) static_assert(false, "GCC 11 or higher is required to support variables and type-scope functions that have a 'requires' clause. This includes a type-scope 'forward' parameter of non-wildcard type, such as 'func: (this, forward s: std::string)', which relies on being able to add a 'requires' clause - in that case, use 'forward s: _' instead if you need the result to compile with GCC 10.")
#else
#define CPP2_REQUIRES(...) requires (__VA_ARGS__)
#define CPP2_REQUIRES_(...) requires (__VA_ARGS__)
#endif
#endif