Skip to content

Commit

Permalink
Restore location of built-in as code
Browse files Browse the repository at this point in the history
Forward declare the `as` function templates for `std::expected` since they're not found by ADL
  • Loading branch information
bluetarpmedia committed Feb 8, 2024
1 parent d09ae59 commit d0dd0c0
Showing 1 changed file with 144 additions and 130 deletions.
274 changes: 144 additions & 130 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,150 @@ inline constexpr auto is( auto const& x, auto&& value ) -> bool
}


//-------------------------------------------------------------------------------------------------------------
// Built-in as
//

// The 'as' cast functions are <To, From> so use that order here
// If it's confusing, we can switch this to <From, To>
template< typename To, typename From >
inline constexpr auto is_narrowing_v =
// [dcl.init.list] 7.1
(std::is_floating_point_v<From> && std::is_integral_v<To>) ||
// [dcl.init.list] 7.2
(std::is_floating_point_v<From> && std::is_floating_point_v<To> && sizeof(From) > sizeof(To)) ||
// [dcl.init.list] 7.3
(std::is_integral_v<From> && std::is_floating_point_v<To>) ||
(std::is_enum_v<From> && std::is_floating_point_v<To>) ||
// [dcl.init.list] 7.4
(std::is_integral_v<From> && std::is_integral_v<To> && sizeof(From) > sizeof(To)) ||
(std::is_enum_v<From> && std::is_integral_v<To> && sizeof(From) > sizeof(To)) ||
// [dcl.init.list] 7.5
(std::is_pointer_v<From> && std::is_same_v<To, bool>);

template <typename... Ts>
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<C> &&
std::is_integral_v<CPP2_TYPEOF(x)> &&
!(static_cast<CPP2_TYPEOF(x)>(static_cast<C>(x)) != x ||
(
(std::is_signed_v<C> != std::is_signed_v<CPP2_TYPEOF(x)>) &&
((static_cast<C>(x) < C{}) != (x < CPP2_TYPEOF(x){}))
)
);

// As
//

template< typename C, auto x >
requires (std::is_arithmetic_v<C> && std::is_arithmetic_v<CPP2_TYPEOF(x)>)
inline constexpr auto as() -> auto
{
if constexpr ( is_castable_v<C, x> ) {
return static_cast<C>(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<C> &&
std::is_floating_point_v<CPP2_TYPEOF(x)> &&
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<C> &&
std::is_integral_v<CPP2_TYPEOF(x)> &&
std::is_signed_v<CPP2_TYPEOF(x)> != std::is_signed_v<C> &&
sizeof(CPP2_TYPEOF(x)) <= sizeof(C)
)
{
const C c = static_cast<C>(x);
Type.enforce( // precondition check: must be round-trippable => not lossy
static_cast<CPP2_TYPEOF(x)>(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<C, std::string> && std::is_integral_v<X>) {
return cpp2::to_string(x);
}
else if constexpr (std::is_same_v<C, X>) {
return x;
}
else if constexpr (std::is_base_of_v<C, X>) {
return static_cast<C const&>(x);
}
else if constexpr (std::is_base_of_v<X, C>) {
return Dynamic_cast<C const&>(x);
}
else if constexpr (
std::is_pointer_v<C>
&& std::is_pointer_v<X>
&& requires { requires std::is_base_of_v<deref_t<X>, deref_t<C>>; }
)
{
return Dynamic_cast<C>(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<X, typename C::value_type>; } ) {
if constexpr( is_narrowing_v<typename C::value_type, X>) {
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<typename T, typename X>
requires std::is_same_v<X, std::expected<T, typename X::error_type>>
constexpr auto as( X const &x ) -> decltype(auto);

template<typename T, typename X>
requires (
std::is_same_v<T, std::unexpected<typename X::error_type>>
&& std::is_same_v<X, std::expected<typename X::value_type, typename X::error_type>>
)
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<C, X>) {
return x;
}
else if constexpr (std::is_base_of_v<C, X>) {
return static_cast<C&>(x);
}
else if constexpr (std::is_base_of_v<X, C>) {
return Dynamic_cast<C&>(x);
}
else {
return as<C>(std::as_const(x));
}
}


//-------------------------------------------------------------------------------------------------------------
// std::variant is and as
//
Expand Down Expand Up @@ -1586,136 +1730,6 @@ constexpr auto as( X const &x ) -> decltype(auto)
#endif


//-------------------------------------------------------------------------------------------------------------
// Built-in as
//

// The 'as' cast functions are <To, From> so use that order here
// If it's confusing, we can switch this to <From, To>
template< typename To, typename From >
inline constexpr auto is_narrowing_v =
// [dcl.init.list] 7.1
(std::is_floating_point_v<From> && std::is_integral_v<To>) ||
// [dcl.init.list] 7.2
(std::is_floating_point_v<From> && std::is_floating_point_v<To> && sizeof(From) > sizeof(To)) ||
// [dcl.init.list] 7.3
(std::is_integral_v<From> && std::is_floating_point_v<To>) ||
(std::is_enum_v<From> && std::is_floating_point_v<To>) ||
// [dcl.init.list] 7.4
(std::is_integral_v<From> && std::is_integral_v<To> && sizeof(From) > sizeof(To)) ||
(std::is_enum_v<From> && std::is_integral_v<To> && sizeof(From) > sizeof(To)) ||
// [dcl.init.list] 7.5
(std::is_pointer_v<From> && std::is_same_v<To, bool>);

template <typename... Ts>
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<C> &&
std::is_integral_v<CPP2_TYPEOF(x)> &&
!(static_cast<CPP2_TYPEOF(x)>(static_cast<C>(x)) != x ||
(
(std::is_signed_v<C> != std::is_signed_v<CPP2_TYPEOF(x)>) &&
((static_cast<C>(x) < C{}) != (x < CPP2_TYPEOF(x){}))
)
);

// As
//

template< typename C, auto x >
requires (std::is_arithmetic_v<C> && std::is_arithmetic_v<CPP2_TYPEOF(x)>)
inline constexpr auto as() -> auto
{
if constexpr ( is_castable_v<C, x> ) {
return static_cast<C>(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<C> &&
std::is_floating_point_v<CPP2_TYPEOF(x)> &&
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<C> &&
std::is_integral_v<CPP2_TYPEOF(x)> &&
std::is_signed_v<CPP2_TYPEOF(x)> != std::is_signed_v<C> &&
sizeof(CPP2_TYPEOF(x)) <= sizeof(C)
)
{
const C c = static_cast<C>(x);
Type.enforce( // precondition check: must be round-trippable => not lossy
static_cast<CPP2_TYPEOF(x)>(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<C, std::string> && std::is_integral_v<X>) {
return cpp2::to_string(x);
}
else if constexpr (std::is_same_v<C, X>) {
return x;
}
else if constexpr (std::is_base_of_v<C, X>) {
return static_cast<C const&>(x);
}
else if constexpr (std::is_base_of_v<X, C>) {
return Dynamic_cast<C const&>(x);
}
else if constexpr (
std::is_pointer_v<C>
&& std::is_pointer_v<X>
&& requires { requires std::is_base_of_v<deref_t<X>, deref_t<C>>; }
)
{
return Dynamic_cast<C>(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<X, typename C::value_type>; } ) {
if constexpr( is_narrowing_v<typename C::value_type, X>) {
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<C, X>) {
return x;
}
else if constexpr (std::is_base_of_v<C, X>) {
return static_cast<C&>(x);
}
else if constexpr (std::is_base_of_v<X, C>) {
return Dynamic_cast<C&>(x);
}
else {
return as<C>(std::as_const(x));
}
}


//-----------------------------------------------------------------------
//
// A variation of GSL's final_action_success / finally
Expand Down

0 comments on commit d0dd0c0

Please sign in to comment.