From d0dd0c0b12977d72d8d432d73fe6a678c7919954 Mon Sep 17 00:00:00 2001 From: Neil Henderson Date: Thu, 8 Feb 2024 12:40:37 +1000 Subject: [PATCH] Restore location of built-in `as` code Forward declare the `as` function templates for `std::expected` since they're not found by ADL --- include/cpp2util.h | 274 ++++++++++++++++++++++++--------------------- 1 file changed, 144 insertions(+), 130 deletions(-) diff --git a/include/cpp2util.h b/include/cpp2util.h index 2131eba13..0cd600a8d 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -1191,6 +1191,150 @@ inline constexpr auto is( auto const& x, auto&& value ) -> bool } +//------------------------------------------------------------------------------------------------------------- +// 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)) <= sizeof(C) + ) + { + const C c = static_cast(x); + Type.enforce( // precondition check: must be round-trippable => 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, deref_t>; } + ) + { + 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; + } +} + +#ifdef __cpp_lib_expected +// Ensure const-ref `as` for `std::expected` is visible before the template definition below since it won't be found by ADL. +template + requires std::is_same_v> +constexpr auto as( X const &x ) -> decltype(auto); + +template + requires ( + std::is_same_v> + && std::is_same_v> + ) +constexpr auto as( X const &x ) -> decltype(auto); +#endif + +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 // @@ -1586,136 +1730,6 @@ constexpr auto as( X const &x ) -> decltype(auto) #endif -//------------------------------------------------------------------------------------------------------------- -// 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)) <= sizeof(C) - ) - { - const C c = static_cast(x); - Type.enforce( // precondition check: must be round-trippable => 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, deref_t>; } - ) - { - 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)); - } -} - - //----------------------------------------------------------------------- // // A variation of GSL's final_action_success / finally