From 36f70ff8c6342d2f5fddd9b1d98e3cca5fec1e6f Mon Sep 17 00:00:00 2001 From: Luke Valenty Date: Thu, 15 Feb 2024 07:10:07 -0800 Subject: [PATCH] add safe_cast --- include/safe.hpp | 1 + include/safe/detail/fwd.hpp | 4 +-- include/safe/safe_cast.hpp | 17 ++++++++++ test/safe/CMakeLists.txt | 2 ++ test/safe/safe_cast.cpp | 33 +++++++++++++++++++ test/safe/safe_cast/CMakeLists.txt | 9 +++++ .../safe_cast/incompatible_range_cast.cpp | 9 +++++ .../safe/safe_cast/incompatible_sign_cast.cpp | 9 +++++ 8 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 include/safe/safe_cast.hpp create mode 100644 test/safe/safe_cast.cpp create mode 100644 test/safe/safe_cast/CMakeLists.txt create mode 100644 test/safe/safe_cast/incompatible_range_cast.cpp create mode 100644 test/safe/safe_cast/incompatible_sign_cast.cpp diff --git a/include/safe.hpp b/include/safe.hpp index 3722100..3b63ab9 100644 --- a/include/safe.hpp +++ b/include/safe.hpp @@ -8,5 +8,6 @@ #include #include #include +#include #include #include diff --git a/include/safe/detail/fwd.hpp b/include/safe/detail/fwd.hpp index 4aa5da5..9f19fdd 100644 --- a/include/safe/detail/fwd.hpp +++ b/include/safe/detail/fwd.hpp @@ -37,12 +37,12 @@ template struct unsafe_cast_ferry { template requires(safe::Var) -[[nodiscard]] constexpr auto unsafe_cast(auto const &src) { +[[nodiscard]] SAFE_INLINE constexpr auto unsafe_cast(auto const &src) { return T{safe::unsafe_cast_ferry{src}}; } template requires(!safe::Var) -[[nodiscard]] constexpr auto unsafe_cast(auto const &src) { +[[nodiscard]] SAFE_INLINE constexpr auto unsafe_cast(auto const &src) { return src; } diff --git a/include/safe/safe_cast.hpp b/include/safe/safe_cast.hpp new file mode 100644 index 0000000..978f501 --- /dev/null +++ b/include/safe/safe_cast.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +#include + +template + requires(std::is_convertible_v) +[[nodiscard]] SAFE_INLINE constexpr To safe_cast(From const &src) { + static_assert(safe::detail::integral_type::requirement >= + From::requirement, + "The safe value must fit within the target value type."); + + return static_cast(src.unsafe_value_); +} diff --git a/test/safe/CMakeLists.txt b/test/safe/CMakeLists.txt index 3b9c18c..12389d4 100644 --- a/test/safe/CMakeLists.txt +++ b/test/safe/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(array) add_subdirectory(var) +add_subdirectory(safe_cast) function(add_test_suites) foreach(test_file ${ARGN}) @@ -34,6 +35,7 @@ add_test_suites( var.cpp match.cpp array.cpp + safe_cast.cpp dsl/add.cpp dsl/divide.cpp dsl/intersection.cpp diff --git a/test/safe/safe_cast.cpp b/test/safe/safe_cast.cpp new file mode 100644 index 0000000..bf3fe00 --- /dev/null +++ b/test/safe/safe_cast.cpp @@ -0,0 +1,33 @@ +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include + +#include + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; + +using namespace safe::interval_types; +using namespace safe::int_types; +using namespace safe::literals; + +TEST(safe_cast_test, cast_same_type) { + auto v = safe_cast(42_s32); + EXPECT_EQ(v, 42); + static_assert(std::is_same_v); +} + +TEST(safe_cast_test, cast_narrower_type) { + auto v = safe_cast(42_s32); + EXPECT_EQ(v, 42); + static_assert(std::is_same_v); +} + +TEST(safe_cast_test, cast_different_sign) { + auto v = safe_cast(99_s32); + EXPECT_EQ(v, 99); + static_assert(std::is_same_v); +} diff --git a/test/safe/safe_cast/CMakeLists.txt b/test/safe/safe_cast/CMakeLists.txt new file mode 100644 index 0000000..600dd41 --- /dev/null +++ b/test/safe/safe_cast/CMakeLists.txt @@ -0,0 +1,9 @@ +function(add_fail_tests) + foreach(name ${ARGN}) + add_compile_fail_test("${name}.cpp" LIBRARIES safe_arithmetic) + endforeach() +endfunction() + +add_fail_tests( + incompatible_sign_cast + incompatible_range_cast) diff --git a/test/safe/safe_cast/incompatible_range_cast.cpp b/test/safe/safe_cast/incompatible_range_cast.cpp new file mode 100644 index 0000000..cec363d --- /dev/null +++ b/test/safe/safe_cast/incompatible_range_cast.cpp @@ -0,0 +1,9 @@ +#include + +#include + +using namespace safe::interval_types; +using namespace safe::int_types; +using namespace safe::literals; + +auto main() -> int { auto v = safe_cast(420_u32); } diff --git a/test/safe/safe_cast/incompatible_sign_cast.cpp b/test/safe/safe_cast/incompatible_sign_cast.cpp new file mode 100644 index 0000000..8a3b50b --- /dev/null +++ b/test/safe/safe_cast/incompatible_sign_cast.cpp @@ -0,0 +1,9 @@ +#include + +#include + +using namespace safe::interval_types; +using namespace safe::int_types; +using namespace safe::literals; + +auto main() -> int { auto v = safe_cast(-99_s32); }