Skip to content

Commit a7eff44

Browse files
JohelEGPhsutter
andauthored
refactor(util): merge built-in is/as type overloads (#956)
* refactor(util): merge built-in `is`/`as` type overloads * Remove dependency on C++20 feature Xcode 13 doesn't have --------- Co-authored-by: Herb Sutter <[email protected]>
1 parent fa0134a commit a7eff44

7 files changed

+168
-190
lines changed

include/cpp2util.h

Lines changed: 110 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,15 @@
268268

269269

270270
#define CPP2_TYPEOF(x) std::remove_cvref_t<decltype(x)>
271+
#if __cplusplus >= 202302L && \
272+
( \
273+
(defined(__clang_major__) && __clang_major__ >= 15) \
274+
|| (defined(__GNUC__) && __GNUC__ >= 12) \
275+
)
276+
#define CPP2_COPY(x) auto(x)
277+
#else
278+
#define CPP2_COPY(x) CPP2_TYPEOF(x)(x)
279+
#endif
271280
#define CPP2_FORWARD(x) std::forward<decltype(x)>(x)
272281
#define CPP2_PACK_EMPTY(x) (sizeof...(x) == 0)
273282
#define CPP2_CONTINUE_BREAK(NAME) goto CONTINUE_##NAME; CONTINUE_##NAME: continue; goto BREAK_##NAME; BREAK_##NAME: break;
@@ -340,6 +349,10 @@ struct aligned_storage {
340349
alignas(Align) unsigned char data[Len];
341350
};
342351

352+
template <typename T>
353+
requires requires { *std::declval<T&>(); }
354+
using deref_t = decltype(*std::declval<T&>());
355+
343356

344357
//-----------------------------------------------------------------------
345358
//
@@ -450,13 +463,13 @@ auto inline Testing = contract_group(
450463

451464

452465
// Check for invalid dereference or indirection which would result in undefined behavior.
453-
//
466+
//
454467
// - Null pointer
455468
// - std::unique_ptr that owns nothing
456469
// - std::shared_ptr with no managed object
457470
// - std::optional with no value
458471
// - std::expected containing an unexpected value
459-
//
472+
//
460473
// Note: For naming simplicity we consider all the above cases to be "null" states so that
461474
// we can write: `*assert_not_null(object)`.
462475
//
@@ -1112,44 +1125,39 @@ constexpr auto is( T const& ) -> bool {
11121125
// Types
11131126
//
11141127
template< typename C, typename X >
1115-
auto is( X const& ) -> bool {
1116-
return false;
1117-
}
1118-
1119-
template< typename C, typename X >
1120-
requires std::is_same_v<C, X>
1121-
auto is( X const& ) -> bool {
1122-
return true;
1123-
}
1124-
1125-
template< typename C, typename X >
1126-
requires (std::is_base_of_v<C, X> && !std::is_same_v<C,X>)
1127-
auto is( X const& ) -> bool {
1128-
return true;
1129-
}
1130-
1131-
template< typename C, typename X >
1132-
requires (
1133-
( std::is_base_of_v<X, C> ||
1134-
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
1135-
) && !std::is_same_v<C,X>)
1136-
auto is( X const& x ) -> bool {
1137-
return Dynamic_cast<C const*>(&x) != nullptr;
1138-
}
1139-
1140-
template< typename C, typename X >
1141-
requires (
1142-
( std::is_base_of_v<X, C> ||
1143-
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
1144-
) && !std::is_same_v<C,X>)
1145-
auto is( X const* x ) -> bool {
1146-
return Dynamic_cast<C const*>(x) != nullptr;
1147-
}
1148-
1149-
template< typename C, typename X >
1150-
requires (requires (X x) { *x; X(); } && std::is_same_v<C, empty>)
11511128
auto is( X const& x ) -> bool {
1152-
return x == X();
1129+
if constexpr (
1130+
std::is_same_v<C, X>
1131+
|| std::is_base_of_v<C, X>
1132+
)
1133+
{
1134+
return true;
1135+
}
1136+
else if constexpr (
1137+
std::is_base_of_v<X, C>
1138+
|| (
1139+
std::is_polymorphic_v<C>
1140+
&& std::is_polymorphic_v<X>
1141+
)
1142+
)
1143+
{
1144+
if constexpr (std::is_pointer_v<X>) {
1145+
return Dynamic_cast<C const*>(x) != nullptr;
1146+
}
1147+
else {
1148+
return Dynamic_cast<C const*>(&x) != nullptr;
1149+
}
1150+
}
1151+
else if constexpr (
1152+
requires { *x; X(); }
1153+
&& std::is_same_v<C, empty>
1154+
)
1155+
{
1156+
return x == X();
1157+
}
1158+
else {
1159+
return false;
1160+
}
11531161
}
11541162

11551163

@@ -1217,11 +1225,6 @@ inline constexpr bool is_castable_v =
12171225
// As
12181226
//
12191227

1220-
template< typename C >
1221-
auto as(auto const&) -> auto {
1222-
return nonesuch;
1223-
}
1224-
12251228
template< typename C, auto x >
12261229
requires (std::is_arithmetic_v<C> && std::is_arithmetic_v<CPP2_TYPEOF(x)>)
12271230
inline constexpr auto as() -> auto
@@ -1233,108 +1236,83 @@ inline constexpr auto as() -> auto
12331236
}
12341237
}
12351238

1236-
template< typename C >
1237-
inline constexpr auto as(auto const& x) -> auto
1238-
requires (
1239+
template< typename C, typename X >
1240+
auto as(X const& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) {
1241+
if constexpr (
12391242
std::is_floating_point_v<C> &&
12401243
std::is_floating_point_v<CPP2_TYPEOF(x)> &&
12411244
sizeof(CPP2_TYPEOF(x)) > sizeof(C)
12421245
)
1243-
{
1244-
return nonesuch;
1245-
}
1246-
1247-
// Signed/unsigned conversions to a not-smaller type are handled as a precondition,
1248-
// and trying to cast from a value that is in the half of the value space that isn't
1249-
// representable in the target type C is flagged as a Type safety contract violation
1250-
template< typename C >
1251-
inline constexpr auto as(auto const& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> auto
1252-
requires (
1246+
{
1247+
return CPP2_COPY(nonesuch);
1248+
}
1249+
// Signed/unsigned conversions to a not-smaller type are handled as a precondition,
1250+
// and trying to cast from a value that is in the half of the value space that isn't
1251+
// representable in the target type C is flagged as a Type safety contract violation
1252+
else if constexpr (
12531253
std::is_integral_v<C> &&
12541254
std::is_integral_v<CPP2_TYPEOF(x)> &&
12551255
std::is_signed_v<CPP2_TYPEOF(x)> != std::is_signed_v<C> &&
12561256
sizeof(CPP2_TYPEOF(x)) <= sizeof(C)
12571257
)
1258-
{
1259-
const C c = static_cast<C>(x);
1260-
Type.enforce( // precondition check: must be round-trippable => not lossy
1261-
static_cast<CPP2_TYPEOF(x)>(c) == x && (c < C{}) == (x < CPP2_TYPEOF(x){}),
1262-
"dynamic lossy narrowing conversion attempt detected" CPP2_SOURCE_LOCATION_ARG
1263-
);
1264-
return c;
1265-
}
1266-
1267-
template< typename C, typename X >
1268-
requires std::is_same_v<C, X>
1269-
auto as( X const& x ) -> decltype(auto) {
1270-
return x;
1271-
}
1272-
1273-
template< typename C, typename X >
1274-
requires std::is_same_v<C, X>
1275-
auto as( X& x ) -> decltype(auto) {
1276-
return x;
1277-
}
1278-
1279-
1280-
template< typename C, typename X >
1281-
auto as(X const& x) -> C
1282-
requires (std::is_same_v<C, std::string> && std::is_integral_v<X>)
1283-
{
1284-
return cpp2::to_string(x);
1285-
}
1286-
1287-
1288-
template< typename C, typename X >
1289-
auto as( X const& x ) -> auto
1290-
requires (!std::is_same_v<C, X> && !std::is_base_of_v<C, X> && requires { C{x}; }
1291-
&& !(std::is_same_v<C, std::string> && std::is_integral_v<X>) // exclude above case
1292-
)
1293-
{
1294-
// Experiment: Recognize the nested `::value_type` pattern for some dynamic library types
1295-
// like std::optional, and try to prevent accidental narrowing conversions even when
1296-
// those types themselves don't defend against them
1297-
if constexpr( requires { requires std::is_convertible_v<X, typename C::value_type>; } ) {
1298-
if constexpr( is_narrowing_v<typename C::value_type, X>) {
1299-
return nonesuch;
1258+
{
1259+
const C c = static_cast<C>(x);
1260+
Type.enforce( // precondition check: must be round-trippable => not lossy
1261+
static_cast<CPP2_TYPEOF(x)>(c) == x && (c < C{}) == (x < CPP2_TYPEOF(x){}),
1262+
"dynamic lossy narrowing conversion attempt detected" CPP2_SOURCE_LOCATION_ARG
1263+
);
1264+
return CPP2_COPY(c);
1265+
}
1266+
else if constexpr (std::is_same_v<C, std::string> && std::is_integral_v<X>) {
1267+
return cpp2::to_string(x);
1268+
}
1269+
else if constexpr (std::is_same_v<C, X>) {
1270+
return x;
1271+
}
1272+
else if constexpr (std::is_base_of_v<C, X>) {
1273+
return static_cast<C const&>(x);
1274+
}
1275+
else if constexpr (std::is_base_of_v<X, C>) {
1276+
return Dynamic_cast<C const&>(x);
1277+
}
1278+
else if constexpr (
1279+
std::is_pointer_v<C>
1280+
&& std::is_pointer_v<X>
1281+
&& requires { requires std::is_base_of_v<deref_t<X>, deref_t<C>>; }
1282+
)
1283+
{
1284+
return Dynamic_cast<C>(x);
1285+
}
1286+
else if constexpr (requires { C{x}; }) {
1287+
// Experiment: Recognize the nested `::value_type` pattern for some dynamic library types
1288+
// like std::optional, and try to prevent accidental narrowing conversions even when
1289+
// those types themselves don't defend against them
1290+
if constexpr( requires { requires std::is_convertible_v<X, typename C::value_type>; } ) {
1291+
if constexpr( is_narrowing_v<typename C::value_type, X>) {
1292+
return nonesuch;
1293+
}
13001294
}
1295+
return C{x};
1296+
}
1297+
else {
1298+
return nonesuch;
13011299
}
1302-
return C{x};
1303-
}
1304-
1305-
template< typename C, typename X >
1306-
requires (std::is_base_of_v<C, X> && !std::is_same_v<C, X>)
1307-
auto as( X& x ) -> C& {
1308-
return x;
1309-
}
1310-
1311-
template< typename C, typename X >
1312-
requires (std::is_base_of_v<C, X> && !std::is_same_v<C, X>)
1313-
auto as( X const& x ) -> C const& {
1314-
return x;
1315-
}
1316-
1317-
template< typename C, typename X >
1318-
requires (std::is_base_of_v<X, C> && !std::is_same_v<C,X>)
1319-
auto as( X& x ) -> C& {
1320-
return Dynamic_cast<C&>(x);
1321-
}
1322-
1323-
template< typename C, typename X >
1324-
requires (std::is_base_of_v<X, C> && !std::is_same_v<C,X>)
1325-
auto as( X const& x ) -> C const& {
1326-
return Dynamic_cast<C const&>(x);
13271300
}
13281301

13291302
template< typename C, typename X >
1330-
requires (
1331-
std::is_pointer_v<C>
1332-
&& std::is_pointer_v<X>
1333-
&& std::is_base_of_v<CPP2_TYPEOF(*std::declval<X>()), CPP2_TYPEOF(*std::declval<C>())>
1334-
&& !std::is_same_v<C, X>
1335-
)
1336-
auto as( X x ) -> C {
1337-
return Dynamic_cast<C>(x);
1303+
auto as( X& x ) -> decltype(auto) {
1304+
if constexpr (std::is_same_v<C, X>) {
1305+
return x;
1306+
}
1307+
else if constexpr (std::is_base_of_v<C, X>) {
1308+
return static_cast<C&>(x);
1309+
}
1310+
else if constexpr (std::is_base_of_v<X, C>) {
1311+
return Dynamic_cast<C&>(x);
1312+
}
1313+
else {
1314+
return as<C>(std::as_const(x));
1315+
}
13381316
}
13391317

13401318

0 commit comments

Comments
 (0)