Skip to content

Commit 9bde75b

Browse files
committed
libfoundation: Add MCNarrowCast & MCNarrow templates
Add two new template functions to libfoundation: - `MCNarrowCast` is a cast-like function template that takes a numeric value of one type and converts it to another, with no checking. It is intended to be when performing deliberate narrowing. In the past we have used "normal" C-style casts, `static_cast`, or constructor-style numeric casts to perform this, but these don't convey "I am deliberately narrowing this numeric value" and are hard to search for. - `MCNarrow` converts a numeric value to a different numeric type, but checks that the conversion didn't lose any information. This can be used instead of hand-coding explicit range checks.
1 parent 561b2b5 commit 9bde75b

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

libfoundation/include/foundation.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,43 @@ typedef struct __MCLocale* MCLocaleRef;
705705
using std::nothrow;
706706
#endif
707707

708+
////////////////////////////////////////////////////////////////////////////////
709+
//
710+
// NARROWING CONVERSIONS
711+
//
712+
713+
#include <utility>
714+
#include <type_traits>
715+
716+
/* A searchable way to do narrowing casts of numeric values (e.g. from
717+
* uint32_t to uint8_t or from uindex_t to std::ptrdiff_t). Use in
718+
* preference to a C-style cast or a raw static_cast. Should be used
719+
* when you're totally certain that overflow/underflow has been
720+
* logically ruled out elsewhere. */
721+
template <typename To, typename From>
722+
inline constexpr To MCNarrowCast(From p_from) noexcept
723+
{
724+
return static_cast<To>(std::forward<From>(p_from));
725+
}
726+
727+
/* Checked narrowing conversion of numeric values. Use when there's a
728+
* possibility that the input value might not fit into the output
729+
* type, and you want to check. Note that this is safe to use in
730+
* generic/template code; if To can represent all values of From, it
731+
* optimises to nothing.*/
732+
template <typename To, typename From>
733+
inline bool MCNarrow(From p_from, To& r_result)
734+
{
735+
To t_to = static_cast<To>(p_from);
736+
if (static_cast<From>(t_to) != p_from)
737+
return false;
738+
if ((std::is_signed<From>::value != std::is_signed<To>::value) &&
739+
((t_to < To{}) != (p_from < From{})))
740+
return false;
741+
r_result = t_to;
742+
return true;
743+
}
744+
708745
////////////////////////////////////////////////////////////////////////////////
709746
//
710747
// MINIMUM FUNCTIONS

libfoundation/test/test_typeconvert.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,30 @@
2222
#include "foundation.h"
2323
#include "foundation-auto.h"
2424

25+
TEST(typeconvert, narrowcast)
26+
{
27+
/* MCNarrowCast never does any checking */
28+
EXPECT_EQ(MCNarrowCast<int>(4.0), 4);
29+
EXPECT_EQ(MCNarrowCast<int>(4.5), 4);
30+
}
31+
32+
TEST(typeconvert, narrow)
33+
{
34+
uint8_t dummy_uint8_t = 0;
35+
EXPECT_TRUE(MCNarrow(INT8_MAX, dummy_uint8_t));
36+
EXPECT_EQ(dummy_uint8_t, INT8_MAX);
37+
EXPECT_FALSE(MCNarrow(UINT8_MAX + 1, dummy_uint8_t));
38+
EXPECT_EQ(dummy_uint8_t, INT8_MAX); /* Unmodified */
39+
EXPECT_FALSE(MCNarrow(INT8_MIN, dummy_uint8_t));
40+
EXPECT_EQ(dummy_uint8_t, INT8_MAX); /* Unmodified */
41+
42+
int dummy_int_t = 0;
43+
EXPECT_TRUE(MCNarrow(3.0, dummy_int_t));
44+
EXPECT_EQ(dummy_int_t, 3);
45+
EXPECT_FALSE(MCNarrow(-1.5, dummy_int_t));
46+
EXPECT_EQ(dummy_int_t, 3); /* Unmodified */
47+
}
48+
2549
TEST(typeconvert, string_integer)
2650
//
2751
// Checks string-to-integer conversion

0 commit comments

Comments
 (0)