From 7940c9f532998e851602f476fff24dc99ca0c53a Mon Sep 17 00:00:00 2001 From: josema Date: Tue, 14 Jan 2025 09:56:28 +0100 Subject: [PATCH 1/2] fix compilation errors with exceptions disabled --- include/cpp2util.h | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/include/cpp2util.h b/include/cpp2util.h index dee5bd2a9..8e2168c61 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -557,6 +557,7 @@ constexpr bool is_escaped(std::string_view s) { } inline bool string_to_int(std::string const& s, int& v, int base = 10) { +#ifndef CPP2_NO_EXCEPTIONS try { v = stoi(s, nullptr, base); return true; @@ -569,6 +570,20 @@ inline bool string_to_int(std::string const& s, int& v, int base = 10) { { return false; } +#else + errno = 0; + char* end = nullptr; + + const long num = std::strtol(s.c_str(), &end, base); + + if (end == s.c_str() || *end != '\0') + return false; // invalid argument + if (errno == ERANGE || num < std::numeric_limits::min() || num > std::numeric_limits::max()) + return false; // out of range + + v = static_cast(num); + return true; +#endif } template @@ -2328,9 +2343,9 @@ class range if (include_last) { if constexpr (std::integral) { if (last == std::numeric_limits::max()) { - throw std::runtime_error( + impl::Throw( std::runtime_error( "range with last == numeric_limits::max() will overflow" - ); + ), "range with last == numeric_limits::max() will overflow"); } } ++last; From ba1913f21eb23a7af45281bf99319d766ced16c8 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Thu, 30 Jan 2025 16:49:15 -0800 Subject: [PATCH 2/2] Add include for cerrno And add an assertion for `end` being not null, which `strtol` guarantees but it's good to check it anyway - and that needs moving contracts earlier in the file --- include/cpp2util.h | 297 ++++++++++++++++++++++++--------------------- 1 file changed, 158 insertions(+), 139 deletions(-) diff --git a/include/cpp2util.h b/include/cpp2util.h index 8e2168c61..a4479800b 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -58,6 +58,7 @@ // to the 'include std' path #if defined(CPP2_IMPORT_STD) && defined(__cpp_lib_modules) import std.compat; + #include // 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 . @@ -254,6 +255,7 @@ #endif #include #include + #include #include #include #include @@ -451,6 +453,144 @@ using _schar = signed char; // normally use i8 instead using _uchar = unsigned char; // normally use u8 instead +//----------------------------------------------------------------------- +// +// An implementation of GSL's narrow_cast with a clearly 'unchecked' name +// +//----------------------------------------------------------------------- +// +namespace impl { + +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 +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)); +} + + +//----------------------------------------------------------------------- +// +// 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 + << where.file_name() << "(" + << where.line() << ") " + << where.function_name() << ": " +#endif + << group << " violation"; + if (msg && msg[0] != '\0') { + std::cerr << ": " << msg; + } + std::cerr << "\n"; + std::terminate(); +} + +auto inline cpp2_default = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Contract", msg CPP2_SOURCE_LOCATION_ARG); + } +); +auto inline bounds_safety = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Bounds safety", msg CPP2_SOURCE_LOCATION_ARG); + } +); +auto inline null_safety = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Null safety", msg CPP2_SOURCE_LOCATION_ARG); + } +); +auto inline type_safety = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Type safety", msg CPP2_SOURCE_LOCATION_ARG); + } +); +auto inline testing = contract_group( + [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { + report_and_terminate("Testing", msg CPP2_SOURCE_LOCATION_ARG); + } +); + + //----------------------------------------------------------------------- // // String utilities @@ -557,7 +697,7 @@ constexpr bool is_escaped(std::string_view s) { } inline bool string_to_int(std::string const& s, int& v, int base = 10) { -#ifndef CPP2_NO_EXCEPTIONS +#ifdef CPP2_NO_EXCEPTIONS try { v = stoi(s, nullptr, base); return true; @@ -574,14 +714,26 @@ inline bool string_to_int(std::string const& s, int& v, int base = 10) { errno = 0; char* end = nullptr; - const long num = std::strtol(s.c_str(), &end, base); + auto const num = std::strtol(s.c_str(), &end, base); - if (end == s.c_str() || *end != '\0') + cpp2_default.enforce(end != nullptr); + if ( + end == s.c_str() + || *end != '\0' + ) + { return false; // invalid argument - if (errno == ERANGE || num < std::numeric_limits::min() || num > std::numeric_limits::max()) + } + if ( + errno == ERANGE + || num < std::numeric_limits::min() + || num > std::numeric_limits::max() + ) + { return false; // out of range + } - v = static_cast(num); + v = unchecked_narrow(num); return true; #endif } @@ -937,94 +1089,6 @@ template } -//----------------------------------------------------------------------- -// -// 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 - << where.file_name() << "(" - << where.line() << ") " - << where.function_name() << ": " -#endif - << group << " violation"; - if (msg && msg[0] != '\0') { - std::cerr << ": " << msg; - } - std::cerr << "\n"; - std::terminate(); -} - -auto inline cpp2_default = contract_group( - [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { - report_and_terminate("Contract", msg CPP2_SOURCE_LOCATION_ARG); - } -); -auto inline bounds_safety = contract_group( - [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { - report_and_terminate("Bounds safety", msg CPP2_SOURCE_LOCATION_ARG); - } -); -auto inline null_safety = contract_group( - [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { - report_and_terminate("Null safety", msg CPP2_SOURCE_LOCATION_ARG); - } -); -auto inline type_safety = contract_group( - [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { - report_and_terminate("Type safety", msg CPP2_SOURCE_LOCATION_ARG); - } -); -auto inline testing = contract_group( - [](CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM)noexcept { - report_and_terminate("Testing", msg CPP2_SOURCE_LOCATION_ARG); - } -); - - namespace impl { template struct dependent_false : std::false_type {}; @@ -1420,7 +1484,7 @@ class deferred_init { public: constexpr deferred_init() noexcept { } constexpr ~deferred_init() noexcept { destroy(); } - constexpr auto value() noexcept -> T& { cpp2_default.enforce(init); return t(); } + 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; } }; @@ -1777,22 +1841,6 @@ constexpr auto is( X const& x, bool (*value)(X const&) ) -> bool { // 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 @@ -2198,35 +2246,6 @@ class finally_presuccess }; -//----------------------------------------------------------------------- -// -// 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