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); }