├── test ├── CMakeLists.txt └── safe │ ├── array │ ├── CMakeLists.txt │ └── array_constant_access_max_violation.cpp │ ├── var │ ├── interval_larger_than_type.cpp │ ├── construct_constant_max_violation.cpp │ ├── construct_constant_min_violation.cpp │ ├── assign_constant_max_violation.cpp │ ├── assign_constant_min_violation.cpp │ ├── assign_lhs_union.cpp │ ├── assign_rhs_union.cpp │ ├── assign_lhs_rhs_union.cpp │ └── CMakeLists.txt │ ├── dsl │ ├── bitwise_invert.cpp │ ├── divide.cpp │ ├── abs.cpp │ ├── detail │ │ ├── triint_gen.hpp │ │ └── triint.cpp │ ├── is_equal.cpp │ ├── intersection.cpp │ ├── is_subset.cpp │ ├── minus.cpp │ ├── add.cpp │ ├── bitwise_xor.cpp │ ├── bitwise_or.cpp │ └── bitwise_and.cpp │ ├── big_integer │ └── detail │ │ ├── bit_not.cpp │ │ ├── negate.cpp │ │ ├── minus.cpp │ │ ├── storage_gen.hpp │ │ ├── plus.cpp │ │ ├── bit_xor.cpp │ │ ├── bit_or.cpp │ │ ├── bit_and.cpp │ │ ├── compare.cpp │ │ ├── multiplies.cpp │ │ ├── properties.hpp │ │ ├── divides.cpp │ │ ├── shift.cpp │ │ └── storage.cpp │ ├── array.cpp │ ├── big_integer_gen.hpp │ ├── CMakeLists.txt │ ├── match.cpp │ ├── big_integer.cpp │ └── var.cpp ├── include ├── safe │ ├── detail │ │ ├── pure.hpp │ │ ├── make_constant.hpp │ │ ├── integral_type.hpp │ │ ├── var_assign_static_assert.hpp │ │ ├── fwd.hpp │ │ ├── concepts.hpp │ │ ├── assume.hpp │ │ └── function.hpp │ ├── algorithm.hpp │ ├── contracts.hpp │ ├── big_integer │ │ ├── interface │ │ │ └── fwd.hpp │ │ └── detail │ │ │ ├── operators.hpp │ │ │ ├── bitwise.hpp │ │ │ ├── plus.hpp │ │ │ ├── algorithms.hpp │ │ │ ├── compare.hpp │ │ │ ├── multiplies.hpp │ │ │ ├── shift.hpp │ │ │ ├── divides.hpp │ │ │ └── storage.hpp │ ├── dsl │ │ ├── eval.hpp │ │ ├── set.hpp │ │ ├── intersection.hpp │ │ ├── is_superset.hpp │ │ ├── primitive.hpp │ │ ├── union.hpp │ │ ├── is_equal.hpp │ │ ├── bitwise_invert.hpp │ │ ├── bit_width.hpp │ │ ├── bitwise_or.hpp │ │ ├── bitwise_and.hpp │ │ ├── bitwise_xor.hpp │ │ ├── minus.hpp │ │ ├── max.hpp │ │ ├── min.hpp │ │ ├── add.hpp │ │ ├── multiply.hpp │ │ ├── ival.hpp │ │ ├── eval_fwd.hpp │ │ ├── divide.hpp │ │ ├── abs.hpp │ │ ├── fwd.hpp │ │ ├── detail │ │ │ ├── eval_is_subset.hpp │ │ │ ├── eval_binary_op.hpp │ │ │ ├── eval_intersection.hpp │ │ │ ├── eval_union.hpp │ │ │ └── triint.hpp │ │ ├── shift_left.hpp │ │ ├── shift_right.hpp │ │ ├── is_subset.hpp │ │ ├── mask.hpp │ │ └── modulo.hpp │ ├── iostream.hpp │ ├── big_integer.hpp │ ├── constant.hpp │ ├── value.hpp │ ├── object.hpp │ ├── dsl.hpp │ ├── algorithm │ │ ├── irange.hpp │ │ └── accumulate.hpp │ ├── match.hpp │ ├── int.hpp │ └── array.hpp ├── CMakeLists.txt └── safe.hpp ├── cmake ├── create-clang-tidiable.sh ├── default.supp └── get_cpm.cmake ├── .gitignore ├── security.md ├── docs ├── index.adoc ├── big_integer.adoc ├── theory.adoc └── motivation.adoc ├── .github ├── dependabot.yml └── workflows │ └── asciidoctor-ghpages.yml ├── CMakeLists.txt ├── LICENSE ├── tools └── gen_release_header.py ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── README.md /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(safe) 2 | -------------------------------------------------------------------------------- /include/safe/detail/pure.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define SAFE_PURE __attribute__((const)) 4 | -------------------------------------------------------------------------------- /cmake/create-clang-tidiable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | echo "#include \"$2\"" > $1 5 | -------------------------------------------------------------------------------- /include/safe/algorithm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include -------------------------------------------------------------------------------- /include/safe/contracts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define SAFE_PRECONDITION(expr) 4 | #define SAFE_INVARIANT(expr) 5 | #define SAFE_POSTCONDITION(expr) -------------------------------------------------------------------------------- /test/safe/array/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_compile_fail_test(array_constant_access_max_violation.cpp LIBRARIES 2 | safe_arithmetic) 3 | -------------------------------------------------------------------------------- /test/safe/var/interval_larger_than_type.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace safe::literals; 4 | 5 | void test() { safe::ival_s8<-1000, 1000> test = 0_s8; } 6 | -------------------------------------------------------------------------------- /test/safe/var/construct_constant_max_violation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace safe::literals; 4 | 5 | void test() { safe::ival_s32<0, 10> test = 11_s32; } 6 | -------------------------------------------------------------------------------- /test/safe/var/construct_constant_min_violation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace safe::literals; 4 | 5 | void test() { safe::ival_s32<0, 10> test = -1_s32; } 6 | -------------------------------------------------------------------------------- /include/safe/big_integer/interface/fwd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace safe::_big_integer::interface { 6 | template struct big_integer; 7 | } 8 | -------------------------------------------------------------------------------- /test/safe/var/assign_constant_max_violation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace safe::literals; 4 | 5 | void test() { 6 | safe::ival_s32<0, 10> test = 0_s32; 7 | test = 20_s32; 8 | } 9 | -------------------------------------------------------------------------------- /test/safe/var/assign_constant_min_violation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace safe::literals; 4 | 5 | void test() { 6 | safe::ival_s32<0, 10> test = 0_s32; 7 | test = -1_s32; 8 | } 9 | -------------------------------------------------------------------------------- /include/safe/dsl/eval.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include -------------------------------------------------------------------------------- /include/safe/iostream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | auto operator<<(std::ostream &os, safe::Var auto var) -> std::ostream & { 8 | return os << var.unsafe_value(); 9 | } 10 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(safe_arithmetic INTERFACE) 2 | target_include_directories(safe_arithmetic 3 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 4 | target_link_libraries(safe_arithmetic INTERFACE boost_mp11) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /cmake-build-* 3 | /venv 4 | /.vscode 5 | /.idea 6 | /.cache 7 | /.DS_Store 8 | .clang-format 9 | .clang-tidy 10 | .cmake-format.yaml 11 | CMakePresets.json 12 | /toolchains 13 | mull.yml 14 | requirements.txt 15 | docs/puppeteer_config.json 16 | -------------------------------------------------------------------------------- /test/safe/var/assign_lhs_union.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace safe::literals; 4 | using safe::ival; 5 | 6 | void test() { 7 | safe::var || ival<20, 30>> a = 0_s32; 8 | safe::var> b = 11_s32; 9 | a = b; 10 | } 11 | -------------------------------------------------------------------------------- /test/safe/var/assign_rhs_union.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace safe::literals; 4 | using safe::ival; 5 | 6 | void test() { 7 | safe::var> a = 1_s32; 8 | safe::var || ival<21, 29>> b = 0_s32; 9 | a = b; 10 | } 11 | -------------------------------------------------------------------------------- /test/safe/array/array_constant_access_max_violation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace safe::literals; 6 | 7 | void test() { 8 | safe::array my_data{}; 9 | int value_one = my_data[10_u64]; 10 | } 11 | -------------------------------------------------------------------------------- /include/safe/big_integer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace safe { 6 | using _big_integer::interface::big_integer; 7 | using _big_integer::interface::common_integer_t; 8 | using _big_integer::interface::to_big_integer; 9 | } // namespace safe -------------------------------------------------------------------------------- /test/safe/var/assign_lhs_rhs_union.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace safe::literals; 4 | using safe::ival; 5 | 6 | void test() { 7 | safe::var || ival<20, 30>> a = 0_s32; 8 | safe::var || ival<21, 29>> b = 11_s32; 9 | a = b; 10 | } 11 | -------------------------------------------------------------------------------- /include/safe/constant.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace safe { 8 | template 9 | constexpr var> constant = 10 | detail::make_constant(); 11 | } -------------------------------------------------------------------------------- /include/safe/detail/make_constant.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace safe::detail { 7 | template 8 | [[nodiscard]] constexpr inline auto make_constant() { 9 | return unsafe_cast>>(value); 10 | } 11 | } // namespace safe::detail -------------------------------------------------------------------------------- /include/safe.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | -------------------------------------------------------------------------------- /include/safe/big_integer/detail/operators.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | -------------------------------------------------------------------------------- /include/safe/dsl/set.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace safe::dsl { 8 | template 9 | using set_t = 10 | union_t...>; 11 | 12 | template constexpr set_t set{}; 13 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/detail/integral_type.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace safe::detail { 10 | template 11 | using integral_type = var::lowest(), 12 | std::numeric_limits::max()>>; 13 | } -------------------------------------------------------------------------------- /security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. 3 | 4 | ## Reporting a Vulnerability 5 | Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). 6 | -------------------------------------------------------------------------------- /include/safe/dsl/intersection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace safe::dsl { 6 | template struct intersection_t : public detail::set_op { 7 | using type = intersection_t; 8 | }; 9 | 10 | template 11 | [[nodiscard]] constexpr auto operator&&(LhsT, RhsT) 12 | -> intersection_t { 13 | return {}; 14 | } 15 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/value.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe { 9 | constexpr auto value(auto value) { 10 | using value_t = decltype(value); 11 | constexpr value_t min = std::numeric_limits::lowest(); 12 | constexpr value_t max = std::numeric_limits::max(); 13 | return unsafe_cast>>(value); 14 | } 15 | } // namespace safe -------------------------------------------------------------------------------- /test/safe/dsl/bitwise_invert.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using ::testing::_; 7 | using ::testing::InSequence; 8 | using ::testing::Return; 9 | 10 | using namespace safe; 11 | using namespace safe::literals; 12 | 13 | TEST(safe_dsl_bitwise_invert, mask_bitwise_invert) { 14 | EXPECT_EQ(~mask<0>, (mask<0, ~0>)); 15 | EXPECT_EQ(~mask<0b1111>, (mask<0b1111, ~0>)); 16 | EXPECT_NE(~mask<0>, mask<0>); 17 | } -------------------------------------------------------------------------------- /include/safe/dsl/is_superset.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace safe::dsl { 8 | template using is_superset = is_subset; 9 | 10 | template 11 | [[nodiscard]] constexpr auto operator>=(LhsT, RhsT) -> bool { 12 | return static_cast(detail::eval_v>); 13 | } 14 | } // namespace safe::dsl 15 | -------------------------------------------------------------------------------- /docs/index.adoc: -------------------------------------------------------------------------------- 1 | = Safe Arithmetic User Guide 2 | Luke Valenty 3 | :revnumber: 0.0 4 | :revdate: April 21, 2023 5 | :source-highlighter: rouge 6 | :rouge-style: base16.dark 7 | :source-language: c++ 8 | :stem: 9 | :toc: left 10 | :toclevels: 4 11 | :sectnums: 12 | :sectnumlevels: 4 13 | :mermaid-puppeteer-config: puppeteer_config.json 14 | 15 | include::motivation.adoc[] 16 | include::overview.adoc[] 17 | include::big_integer.adoc[] 18 | include::api_reference.adoc[] 19 | include::theory.adoc[] 20 | -------------------------------------------------------------------------------- /test/safe/dsl/divide.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using ::testing::_; 7 | using ::testing::InSequence; 8 | using ::testing::Return; 9 | 10 | using namespace safe; 11 | using namespace safe::literals; 12 | 13 | TEST(safe_dsl_divide, divide_two_ival_constants) { 14 | EXPECT_EQ((ival<48, 48> / ival<4, 4>), (ival<12, 12>)); 15 | } 16 | 17 | TEST(safe_dsl_divide, add_two_intervals) { 18 | EXPECT_EQ((ival<10, 40> / ival<1, 5>), (ival<2, 40>)); 19 | } 20 | -------------------------------------------------------------------------------- /include/safe/big_integer/detail/bitwise.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe::_big_integer::detail { 9 | constexpr static auto bit_and = zip_transform(std::bit_and{}); 10 | constexpr static auto bit_or = zip_transform(std::bit_or{}); 11 | constexpr static auto bit_xor = zip_transform(std::bit_xor{}); 12 | constexpr static auto bit_not = transform(std::bit_not{}); 13 | } // namespace safe::_big_integer::detail -------------------------------------------------------------------------------- /include/safe/object.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace safe { 4 | template struct object { 5 | T value; 6 | 7 | [[nodiscard]] auto operator->() -> T * { return &value; } 8 | 9 | [[nodiscard]] auto operator*() -> T & { return value; } 10 | }; 11 | 12 | template struct field_t { 13 | constexpr auto operator&&(auto) const -> bool { return false; } 14 | }; 15 | 16 | template 17 | constexpr auto field = field_t{}; 18 | } // namespace safe 19 | -------------------------------------------------------------------------------- /include/safe/dsl/primitive.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe::dsl::detail { 9 | template constexpr bool is_primitive_v = false; 10 | 11 | template 12 | constexpr bool is_primitive_v> = true; 13 | 14 | template 15 | constexpr bool is_primitive_v> = true; 16 | 17 | template 18 | concept Primitive = is_primitive_v; 19 | } // namespace safe::dsl::detail -------------------------------------------------------------------------------- /include/safe/dsl/union.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace safe::dsl { 7 | template struct union_t : public detail::set_op { 8 | using type = union_t; 9 | 10 | template 11 | [[nodiscard]] SAFE_PURE constexpr static auto check(T value) -> bool { 12 | return (Intervals::check(value) || ...); 13 | } 14 | }; 15 | 16 | template 17 | [[nodiscard]] constexpr auto operator||(LhsT, RhsT) -> union_t { 18 | return {}; 19 | } 20 | } // namespace safe::dsl 21 | -------------------------------------------------------------------------------- /include/safe/dsl/is_equal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe::dsl { 9 | template 10 | [[nodiscard]] constexpr auto operator==(LhsT lhs, RhsT rhs) -> bool { 11 | auto const simp_lhs = detail::simp(lhs); 12 | auto const simp_rhs = detail::simp(rhs); 13 | 14 | return (simp_lhs <= simp_rhs) && (simp_lhs >= simp_rhs); 15 | } 16 | 17 | template 18 | [[nodiscard]] constexpr auto operator!=(LhsT lhs, RhsT rhs) -> bool { 19 | return !(lhs == rhs); 20 | } 21 | } // namespace safe::dsl 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "gitsubmodule" 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /include/safe/dsl/bitwise_invert.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace safe::dsl { 9 | template struct bitwise_invert {}; 10 | 11 | template 12 | struct bitwise_invert : public detail::unary_op { 13 | using mask_arg_t = detail::to_mask_t; 14 | constexpr static auto value = ~mask_arg_t::value; 15 | using type = mask_t; 16 | }; 17 | 18 | template 19 | [[nodiscard]] constexpr auto operator~(T) -> bitwise_invert { 20 | return {}; 21 | } 22 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/detail/var_assign_static_assert.hpp: -------------------------------------------------------------------------------- 1 | #pragma 2 | 3 | namespace safe { 4 | template struct lhs_req { 5 | constexpr static U value{}; 6 | }; 7 | 8 | template struct rhs_req { 9 | constexpr static U value{}; 10 | }; 11 | 12 | template struct rhs_must_be_subset_of_lhs { 13 | constexpr static bool value = LhsT::value >= RhsT::value; 14 | }; 15 | 16 | constexpr inline void static_assert_assign_requirements(auto lhs, auto rhs) { 17 | static_assert( 18 | rhs_must_be_subset_of_lhs, 19 | rhs_req>::value); 20 | } 21 | } // namespace safe -------------------------------------------------------------------------------- /include/safe/dsl/bit_width.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace safe::dsl { 12 | template struct bit_width_t {}; 13 | 14 | template struct bit_width_t : public detail::unary_op { 15 | using val = detail::to_mask_t; 16 | 17 | using type = ival_t; 19 | }; 20 | 21 | template 22 | [[nodiscard]] constexpr auto bit_width(T) -> bit_width_t { 23 | return {}; 24 | } 25 | } // namespace safe::dsl -------------------------------------------------------------------------------- /test/safe/big_integer/detail/bit_not.cpp: -------------------------------------------------------------------------------- 1 | #include "properties.hpp" 2 | #include "storage_gen.hpp" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace safe::_big_integer::detail { 11 | RC_GTEST_PROP(bit_not, not_not_is_self, (storage<128> a)) { 12 | storage<128> r{}; 13 | bit_not(r, a); 14 | bit_not(r, r); 15 | RC_ASSERT(r == a); 16 | } 17 | 18 | RC_GTEST_PROP(bit_not, is_not, (int64_t a)) { 19 | storage<64> actual{}; 20 | bit_not(actual, make_storage(a)); 21 | storage<64> expected = make_storage(~a); 22 | RC_ASSERT(actual == expected); 23 | } 24 | } // namespace safe::_big_integer::detail -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | 3 | project(safe_arithmetic LANGUAGES CXX) 4 | 5 | include(cmake/get_cpm.cmake) 6 | if(PROJECT_IS_TOP_LEVEL) 7 | cpmaddpackage("gh:intel/cicd-repo-infrastructure#dev") 8 | else() 9 | cpmaddpackage("gh:intel/cicd-repo-infrastructure#3e2bef0") 10 | endif() 11 | 12 | add_versioned_package("gh:boostorg/mp11#boost-1.83.0") 13 | 14 | add_library(safe_arithmetic INTERFACE) 15 | target_compile_features(safe_arithmetic INTERFACE cxx_std_20) 16 | target_include_directories(safe_arithmetic INTERFACE include) 17 | target_link_libraries_system(safe_arithmetic INTERFACE boost_mp11) 18 | 19 | if(PROJECT_IS_TOP_LEVEL) 20 | add_docs(docs) 21 | clang_tidy_interface(safe_arithmetic) 22 | add_subdirectory(test) 23 | endif() 24 | -------------------------------------------------------------------------------- /test/safe/var/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_compile_fail_test(assign_constant_max_violation.cpp LIBRARIES 2 | safe_arithmetic) 3 | add_compile_fail_test(assign_constant_min_violation.cpp LIBRARIES 4 | safe_arithmetic) 5 | add_compile_fail_test(construct_constant_max_violation.cpp LIBRARIES 6 | safe_arithmetic) 7 | add_compile_fail_test(construct_constant_min_violation.cpp LIBRARIES 8 | safe_arithmetic) 9 | add_compile_fail_test(interval_larger_than_type.cpp LIBRARIES safe_arithmetic) 10 | add_compile_fail_test(assign_lhs_rhs_union.cpp LIBRARIES safe_arithmetic) 11 | add_compile_fail_test(assign_lhs_union.cpp LIBRARIES safe_arithmetic) 12 | add_compile_fail_test(assign_rhs_union.cpp LIBRARIES safe_arithmetic) 13 | -------------------------------------------------------------------------------- /test/safe/big_integer/detail/negate.cpp: -------------------------------------------------------------------------------- 1 | #include "properties.hpp" 2 | #include "storage_gen.hpp" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace safe::_big_integer::detail { 11 | RC_GTEST_PROP(negate, negate_negate_is_self, (storage<128> a)) { 12 | storage<128> r{}; 13 | negate(r, a); 14 | negate(r, r); 15 | RC_ASSERT(r == a); 16 | } 17 | 18 | RC_GTEST_PROP(negate, is_negate, (int64_t a)) { 19 | RC_PRE(negation_will_not_overflow(a)); 20 | storage<64> actual{}; 21 | negate(actual, make_storage(a)); 22 | storage<64> expected = make_storage(-a); 23 | RC_ASSERT(actual == expected); 24 | } 25 | } // namespace safe::_big_integer::detail 26 | -------------------------------------------------------------------------------- /test/safe/dsl/abs.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using ::testing::_; 7 | using ::testing::InSequence; 8 | using ::testing::Return; 9 | 10 | using namespace safe; 11 | using namespace safe::literals; 12 | 13 | TEST(safe_dsl_abs, ival_identity) { 14 | EXPECT_EQ(abs(ival<10, 20>), (ival<10, 20>)); 15 | } 16 | 17 | TEST(safe_dsl_abs, ival_all_negative) { 18 | EXPECT_EQ(abs(ival<-30, -10>), (ival<10, 30>)); 19 | } 20 | 21 | TEST(safe_dsl_abs, ival_straddle_min) { 22 | EXPECT_EQ(abs(ival<-30, 10>), (ival<0, 30>)); 23 | } 24 | 25 | TEST(safe_dsl_abs, ival_straddle_max) { 26 | EXPECT_EQ(abs(ival<-30, 40>), (ival<0, 40>)); 27 | } 28 | 29 | TEST(safe_dsl_abs, ival_straddle_eq) { 30 | EXPECT_EQ(abs(ival<-50, 50>), (ival<0, 50>)); 31 | } 32 | -------------------------------------------------------------------------------- /cmake/default.supp: -------------------------------------------------------------------------------- 1 | { 2 | gmock_leak_1 3 | Memcheck:Leak 4 | match-leak-kinds: reachable 5 | fun:_Znwm 6 | fun:_ZN7testing12_GLOBAL__N_128UninterestingCallReactionMapEv 7 | } 8 | { 9 | gmock_leak_2 10 | Memcheck:Leak 11 | match-leak-kinds: reachable 12 | fun:_Znwm 13 | fun:_ZNSt15__new_allocatorIPNSt8__detail15_Hash_node_baseEE8allocateEmPKv 14 | fun:_ZNSt16allocator_traitsISaIPNSt8__detail15_Hash_node_baseEEE8allocateERS3_m 15 | fun:_ZNSt8__detail16_Hashtable_allocISaINS_10_Hash_nodeISt4pairIKmN7testing8internal12CallReactionEELb0EEEEE19_M_allocate_bucketsEm 16 | } 17 | { 18 | gunit_leak_1 19 | Memcheck:Leak 20 | match-leak-kinds: reachable 21 | fun:_Znwm 22 | fun:_ZNSt15__new_allocatorINSt8__detail10_Hash_nodeISt4pairIKmN7testing8internal12CallReactionEELb0EEEE8allocateEmPKv 23 | } 24 | -------------------------------------------------------------------------------- /include/safe/dsl/bitwise_or.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace safe::dsl { 9 | template 10 | struct bitwise_or : public detail::binary_op {}; 11 | 12 | template 13 | [[nodiscard]] constexpr auto operator|(LhsT, RhsT) -> bitwise_or { 14 | return {}; 15 | } 16 | 17 | template 18 | struct bitwise_or : public detail::binary_op { 19 | using lhs = detail::to_mask_t; 20 | using rhs = detail::to_mask_t; 21 | constexpr static auto value = lhs::value | rhs::value; 22 | using type = mask_t; 23 | }; 24 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/bitwise_and.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace safe::dsl { 9 | template 10 | struct bitwise_and : public detail::binary_op {}; 11 | 12 | template 13 | struct bitwise_and : public detail::binary_op { 14 | using lhs = detail::to_mask_t; 15 | using rhs = detail::to_mask_t; 16 | constexpr static auto value = lhs::value & rhs::value; 17 | using type = mask_t; 18 | }; 19 | 20 | template 21 | [[nodiscard]] constexpr auto operator&(LhsT, RhsT) -> bitwise_and { 22 | return {}; 23 | } 24 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/bitwise_xor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace safe::dsl { 9 | template 10 | struct bitwise_xor : public detail::binary_op {}; 11 | 12 | template 13 | struct bitwise_xor : public detail::binary_op { 14 | using lhs = detail::to_mask_t; 15 | using rhs = detail::to_mask_t; 16 | constexpr static auto value = lhs::value ^ rhs::value; 17 | using type = mask_t; 18 | }; 19 | 20 | template 21 | [[nodiscard]] constexpr auto operator^(LhsT, RhsT) -> bitwise_xor { 22 | return {}; 23 | } 24 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/minus.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace safe::dsl { 8 | template struct minus : public detail::binary_op {}; 9 | 10 | template 11 | struct minus : public detail::binary_op { 12 | using type = ival_t; 13 | }; 14 | 15 | template 16 | struct minus : public detail::binary_op { 17 | constexpr static auto value = LhsT::value - RhsT::value; 18 | using type = mask_t; 19 | }; 20 | 21 | template 22 | [[nodiscard]] constexpr auto operator-(LhsT, RhsT) -> minus { 23 | return {}; 24 | } 25 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/max.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe::dsl { 9 | template struct max_t : public detail::binary_op {}; 10 | 11 | template 12 | struct max_t, ival_t> 13 | : public detail::binary_op { 14 | using type = ival_t< 15 | std::max>( 16 | lhs_min, rhs_min), 17 | std::max>( 18 | lhs_max, rhs_max)>; 19 | }; 20 | 21 | template 22 | [[nodiscard]] constexpr auto max(LhsT, RhsT) -> max_t { 23 | return {}; 24 | } 25 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/min.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe::dsl { 9 | template struct min_t : public detail::binary_op {}; 10 | 11 | template 12 | struct min_t, ival_t> 13 | : public detail::binary_op { 14 | using type = ival_t< 15 | std::min>( 16 | lhs_min, rhs_min), 17 | std::min>( 18 | lhs_max, rhs_max)>; 19 | }; 20 | 21 | template 22 | [[nodiscard]] constexpr auto min(LhsT, RhsT) -> min_t { 23 | return {}; 24 | } 25 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/add.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace safe::dsl { 9 | template struct add : public detail::binary_op {}; 10 | 11 | template 12 | struct add : public detail::binary_op { 13 | using type = ival_t; 14 | }; 15 | 16 | template 17 | struct add : public detail::binary_op { 18 | constexpr static auto value = LhsT::value + RhsT::value; 19 | using type = mask_t; 20 | }; 21 | 22 | template 23 | [[nodiscard]] constexpr auto operator+(LhsT, RhsT) -> add { 24 | return {}; 25 | } 26 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/multiply.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe::dsl { 9 | template struct multiply : public detail::binary_op {}; 10 | 11 | template 12 | struct multiply, ival_t> 13 | : public detail::binary_op { 14 | 15 | using type = ival_t, 17 | detail::max>; 19 | }; 20 | 21 | template 22 | [[nodiscard]] constexpr auto operator*(LhsT, RhsT) -> multiply { 23 | return {}; 24 | } 25 | } // namespace safe::dsl -------------------------------------------------------------------------------- /test/safe/dsl/detail/triint_gen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace safe::dsl::detail { 8 | template 9 | std::ostream &operator<<(std::ostream &os, triint const &t) { 10 | os << "triint(" << std::setfill('0') << std::hex << std::setw(sizeof(T) * 2) 11 | << t.var_bits_ << ", " << std::setfill('0') << std::hex 12 | << std::setw(sizeof(T) * 2) << t.const_bits_ << ")"; 13 | 14 | return os; 15 | } 16 | } // namespace safe::dsl::detail 17 | 18 | namespace rc { 19 | template struct Arbitrary> { 20 | static Gen> arbitrary() { 21 | return gen::apply( 22 | [](T var_bits, T const_bits) { 23 | return safe::dsl::triint{var_bits, const_bits}; 24 | }, 25 | gen::arbitrary(), gen::arbitrary()); 26 | } 27 | }; 28 | } // namespace rc -------------------------------------------------------------------------------- /include/safe/dsl/ival.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace safe::dsl { 10 | template struct ival_t : public detail::primitive { 11 | using type = ival_t; 12 | 13 | constexpr static auto min = Min; 14 | constexpr static auto max = Max; 15 | 16 | // static_assert(min <= max); 17 | 18 | [[nodiscard]] SAFE_PURE constexpr static auto check(auto value) -> bool { 19 | return value >= min && value <= max; 20 | } 21 | }; 22 | 23 | template 24 | constexpr ival_t ival{}; 25 | 26 | template constexpr bool is_ival_v = false; 27 | 28 | template constexpr bool is_ival_v> = true; 29 | 30 | template 31 | concept Interval = is_ival_v; 32 | } // namespace safe::dsl 33 | -------------------------------------------------------------------------------- /test/safe/array.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using ::testing::_; 7 | using ::testing::InSequence; 8 | using ::testing::Return; 9 | 10 | using namespace safe::interval_types; 11 | using namespace safe::int_types; 12 | using namespace safe::literals; 13 | 14 | TEST(safe_array_test, construction) { safe::array t{}; } 15 | 16 | TEST(safe_array_test, bracket_op) { 17 | safe::array t{}; 18 | 19 | t[12_s32] = 42; 20 | 21 | EXPECT_EQ(t[12_s32], 42); 22 | } 23 | 24 | TEST(safe_array_test, const_bracket_op) { 25 | safe::array const t{0, 13, 2}; 26 | 27 | EXPECT_EQ(t[1_s32], 13); 28 | } 29 | 30 | TEST(safe_array_test, at) { 31 | safe::array t{}; 32 | 33 | t[12_s32] = 42; 34 | 35 | EXPECT_EQ(t.at(12_s32), 42); 36 | } 37 | 38 | TEST(safe_array_test, const_at) { 39 | safe::array const t{0, 13, 2}; 40 | 41 | EXPECT_EQ(t.at(1_s32), 13); 42 | } 43 | -------------------------------------------------------------------------------- /test/safe/dsl/detail/triint.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace safe::dsl::detail { 10 | template using big_triint = triint>; 11 | 12 | RC_GTEST_PROP(triint, bit_and_is_commutative, 13 | (big_triint<256> a, big_triint<256> b)) { 14 | RC_ASSERT((a & b) == (b & a)); 15 | } 16 | 17 | RC_GTEST_PROP(triint, bit_and_is_associative, 18 | (big_triint<256> a, big_triint<256> b, big_triint<256> c)) { 19 | RC_ASSERT(((a & b) & c) == (a & (b & c))); 20 | } 21 | 22 | RC_GTEST_PROP(triint, bitwise_demorgan_laws, 23 | (big_triint<256> a, big_triint<256> b)) { 24 | RC_ASSERT(~(a | b) == (~a & ~b)); 25 | RC_ASSERT(~(a & b) == (~a | ~b)); 26 | } 27 | } // namespace safe::dsl::detail -------------------------------------------------------------------------------- /test/safe/big_integer/detail/minus.cpp: -------------------------------------------------------------------------------- 1 | #include "properties.hpp" 2 | #include "storage_gen.hpp" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace safe::_big_integer::detail { 11 | RC_GTEST_PROP(minus, identity, (storage<128> a)) { 12 | identity<128, storage<32>{}>(minus, a); 13 | } 14 | 15 | RC_GTEST_PROP(minus, self_is_domination, (storage<128> a)) { 16 | storage<128> actual{}; 17 | minus(actual, a, a); 18 | storage<128> zero{}; 19 | RC_ASSERT(actual == zero); 20 | } 21 | 22 | RC_GTEST_PROP(minus, is_minus, (int64_t a, int64_t b)) { 23 | RC_PRE(subtraction_will_not_overflow(a, b)); 24 | storage<64> actual{}; 25 | minus(actual, make_storage(a), make_storage(b)); 26 | storage<64> expected = make_storage(a - b); 27 | RC_ASSERT(actual == expected); 28 | } 29 | } // namespace safe::_big_integer::detail 30 | -------------------------------------------------------------------------------- /test/safe/dsl/is_equal.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using ::testing::_; 7 | using ::testing::InSequence; 8 | using ::testing::Return; 9 | 10 | using namespace safe; 11 | using namespace safe::literals; 12 | 13 | TEST(safe_dsl_is_equal, simple_ivals) { 14 | EXPECT_EQ((ival<10, 20>), (ival<10, 20>)); 15 | 16 | EXPECT_NE((ival<10, 20>), (ival<10, 10>)); 17 | } 18 | 19 | TEST(safe_dsl_is_equal, union_ivals) { 20 | EXPECT_EQ((ival<10, 20> || ival<40, 50>), (ival<10, 20> || ival<40, 50>)); 21 | 22 | EXPECT_EQ((ival<10, 20> || ival<40, 50>), (ival<40, 50> || ival<10, 20>)); 23 | } 24 | 25 | TEST(safe_dsl_is_equal, differing_ival_types) { 26 | EXPECT_EQ((ival<10, 20>), (ival<10u, 20u>)); 27 | 28 | EXPECT_EQ((ival<10ll, 20ll>), (ival<10, 20>)); 29 | } 30 | 31 | TEST(safe_dsl_is_equal, with_delimeter) { 32 | EXPECT_EQ(1'2'3'4'5_u16, 12345_u16); 33 | EXPECT_EQ(0xc'0'ff'ee_u32, 0xc0ffee_u32); 34 | } 35 | -------------------------------------------------------------------------------- /include/safe/dsl/eval_fwd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace safe::dsl::detail { 12 | using namespace boost::mp11; 13 | 14 | template struct eval { 15 | using type = typename T::type; 16 | constexpr static type value{}; 17 | }; 18 | 19 | template using eval_t = typename eval::type; 20 | 21 | template constexpr auto eval_v = eval::value; 22 | 23 | template [[nodiscard]] constexpr auto simp(T) { 24 | return eval_t{}; 25 | } 26 | 27 | template 28 | struct is_union : public std::integral_constant {}; 29 | 30 | template 31 | struct is_union> : public std::integral_constant {}; 32 | 33 | template constexpr bool is_union_v = is_union{}; 34 | } // namespace safe::dsl::detail -------------------------------------------------------------------------------- /cmake/get_cpm.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.40.2) 6 | set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /test/safe/dsl/intersection.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using ::testing::_; 7 | using ::testing::InSequence; 8 | using ::testing::Return; 9 | 10 | using namespace safe; 11 | using namespace safe::literals; 12 | 13 | TEST(safe_dsl_intersection, eval_ival_overlap) { 14 | EXPECT_EQ((ival<0, 20> && ival<10, 30>), (ival<10, 20>)); 15 | } 16 | 17 | TEST(safe_dsl_intersection, eval_lhs_ival_contained) { 18 | EXPECT_EQ((ival<15, 25> && ival<10, 30>), (ival<15, 25>)); 19 | } 20 | 21 | TEST(safe_dsl_intersection, eval_rhs_ival_contained) { 22 | EXPECT_EQ((ival<15, 25> && ival<19, 21>), (ival<19, 21>)); 23 | } 24 | 25 | TEST(safe_dsl_intersection, eval_single_int_1) { 26 | EXPECT_EQ((ival<15, 25> && ival<25, 35>), (ival<25, 25>)); 27 | } 28 | 29 | TEST(safe_dsl_intersection, eval_single_int_2) { 30 | EXPECT_EQ((ival<35, 50> && ival<25, 35>), (ival<35, 35>)); 31 | } 32 | 33 | TEST(safe_dsl_intersection, eval_three_ival_overlap) { 34 | EXPECT_EQ((ival<0, 20> && ival<10, 30> && ival<-100, 15>), (ival<10, 15>)); 35 | } -------------------------------------------------------------------------------- /include/safe/dsl/divide.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe::dsl { 9 | template struct divide : public detail::binary_op {}; 10 | 11 | template 12 | struct divide, ival_t> 13 | : public detail::binary_op { 14 | static_assert((rhs_min < 0 && rhs_max < 0) || (rhs_min > 0 && rhs_max > 0), 15 | "RHS of division operator must be guaranteed to not be '0'."); 16 | 17 | using type = ival_t, 19 | detail::max>; 21 | }; 22 | 23 | template 24 | [[nodiscard]] constexpr auto operator/(LhsT, RhsT) -> divide { 25 | return {}; 26 | } 27 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/abs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace safe::dsl { 11 | namespace detail { 12 | [[nodiscard]] constexpr auto abs(auto value) { 13 | if (value < 0) { 14 | return -value; 15 | } 16 | return decltype(-value){value}; 17 | } 18 | } // namespace detail 19 | 20 | template struct abs_t {}; 21 | 22 | template struct abs_t : public detail::unary_op { 23 | using val = detail::to_ival_t; 24 | 25 | constexpr static bool straddles_zero = val::min < 0 && val::max > 0; 26 | 27 | using type = ival_t; 31 | }; 32 | 33 | template [[nodiscard]] constexpr auto abs(T) -> abs_t { 34 | return {}; 35 | } 36 | } // namespace safe::dsl 37 | -------------------------------------------------------------------------------- /test/safe/dsl/is_subset.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using ::testing::_; 7 | using ::testing::InSequence; 8 | using ::testing::Return; 9 | 10 | using namespace safe; 11 | using namespace safe::literals; 12 | 13 | TEST(safe_dsl_is_subset, superset_v_subset_op) { 14 | EXPECT_TRUE(!(ival<0, 100> >= ival<0, 101>)); 15 | EXPECT_TRUE(!(ival<0, 100> >= ival<200, 300>)); 16 | EXPECT_TRUE(!(ival<0, 100> <= ival<200, 300>)); 17 | EXPECT_TRUE(!(ival<0, 200> >= ival<100, 300>)); 18 | EXPECT_TRUE(!(ival<0, 200> <= ival<100, 300>)); 19 | } 20 | 21 | TEST(safe_dsl_is_subset, superset_op) { 22 | EXPECT_TRUE((ival<0, 100> >= ival<10, 90>)); 23 | EXPECT_TRUE((ival<0, 100> >= ival<0, 100>)); 24 | } 25 | 26 | TEST(safe_dsl_is_subset, superset_union_simplification) { 27 | EXPECT_TRUE((ival<0, 100> >= (ival<10, 20> || ival<30, 40>))); 28 | EXPECT_TRUE(((ival<0, 100> || ival<200, 300>) >= ival<10, 20>)); 29 | EXPECT_TRUE(((ival<0, 100> || ival<200, 300>) >= ival<250, 300>)); 30 | EXPECT_TRUE( 31 | ((ival<0, 100> || ival<200, 300>) >= (ival<10, 20> || ival<250, 300>))); 32 | } -------------------------------------------------------------------------------- /test/safe/big_integer_gen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace safe::_big_integer::interface { 9 | template 10 | std::ostream &operator<<(std::ostream &os, big_integer const &b) { 11 | auto const &s = b.unsafe_storage; 12 | auto i = s.num_elems; 13 | do { 14 | i--; 15 | os << std::setfill('0') << std::hex << std::setw(8) << s.get(i) << " "; 16 | } while (i > 0); 17 | return os; 18 | } 19 | } // namespace safe::_big_integer::interface 20 | 21 | namespace rc { 22 | template 23 | using safe_big_integer = safe::_big_integer::interface::big_integer; 24 | 25 | template struct Arbitrary> { 26 | using T = safe_big_integer; 27 | static Gen arbitrary() { 28 | using src = safe::_big_integer::detail::storage; 29 | 30 | return gen::map(gen::arbitrary(), 31 | [](src const &v) { return T{v}; }); 32 | } 33 | }; 34 | } // namespace rc -------------------------------------------------------------------------------- /include/safe/dsl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace safe { 29 | using safe::dsl::ival; 30 | using safe::dsl::ival_t; 31 | 32 | using safe::dsl::mask; 33 | using safe::dsl::mask_t; 34 | 35 | using safe::dsl::set; 36 | using safe::dsl::set_t; 37 | 38 | using safe::dsl::intersection_t; 39 | using safe::dsl::union_t; 40 | } // namespace safe -------------------------------------------------------------------------------- /include/safe/big_integer/detail/plus.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace safe::_big_integer::detail { 10 | constexpr static auto plus = stateful_zip_transform( 11 | elem_t{}, 12 | [](elem_t &carry, double_elem_t const &lhs, 13 | double_elem_t const &rhs) -> double_elem_t { 14 | double_elem_t const result = 15 | lhs + rhs + static_cast(carry); 16 | 17 | carry = result >> 32u; 18 | return result & 0xffff'ffffu; 19 | }); 20 | 21 | constexpr static auto negate = [](auto &result, auto const &value) -> void { 22 | std::remove_cvref_t not_value{}; 23 | bit_not(not_value, value); 24 | plus(result, not_value, make_storage(1)); 25 | }; 26 | 27 | constexpr static auto minus = [](auto &result, auto const &lhs, 28 | auto const &rhs) -> void { 29 | std::remove_cvref_t negative_rhs{}; 30 | negate(negative_rhs, rhs); 31 | plus(result, lhs, negative_rhs); 32 | }; 33 | } // namespace safe::_big_integer::detail 34 | -------------------------------------------------------------------------------- /test/safe/big_integer/detail/storage_gen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace safe::_big_integer::detail { 16 | template 17 | std::ostream &operator<<(std::ostream &os, storage const &s) { 18 | auto i = s.num_elems; 19 | do { 20 | i--; 21 | os << std::setfill('0') << std::hex << std::setw(8) << s.get(i) << " "; 22 | } while (i > 0); 23 | return os; 24 | } 25 | } // namespace safe::_big_integer::detail 26 | 27 | namespace rc { 28 | template 29 | using safe_storage = safe::_big_integer::detail::storage; 30 | 31 | template struct Arbitrary> { 32 | using T = safe_storage; 33 | static Gen arbitrary() { 34 | using src_array = std::array; 35 | 36 | return gen::map(gen::arbitrary(), 37 | [](src_array const &values) { return T{values}; }); 38 | } 39 | }; 40 | } // namespace rc 41 | -------------------------------------------------------------------------------- /include/safe/dsl/fwd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace safe::dsl::detail { 6 | struct binary_op {}; 7 | struct unary_op {}; 8 | struct primitive {}; 9 | struct set_op {}; 10 | 11 | // FIXME: make the min/max template vars take an arbitrary num of args 12 | template 13 | constexpr auto min2 = [] { 14 | if constexpr (lhs <= rhs) { 15 | return lhs; 16 | } else { 17 | return rhs; 18 | } 19 | }(); 20 | 21 | template 22 | constexpr auto min = [] { return min2, min2>; }(); 23 | 24 | template 25 | constexpr auto max2 = [] { 26 | if constexpr (lhs > rhs) { 27 | return lhs; 28 | } else { 29 | return rhs; 30 | } 31 | }(); 32 | 33 | template 34 | constexpr auto max = [] { return max2, max2>; }(); 35 | } // namespace safe::dsl::detail 36 | 37 | namespace safe::dsl { 38 | template 39 | concept Operand = std::is_base_of_v || 40 | std::is_base_of_v || 41 | std::is_base_of_v || 42 | std::is_base_of_v; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /include/safe/detail/fwd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SAFE_INLINE 4 | #define SAFE_INLINE inline 5 | #endif 6 | 7 | namespace safe { 8 | template struct var; 9 | 10 | template constexpr bool is_var_v = false; 11 | 12 | template 13 | constexpr bool is_var_v> = true; 14 | 15 | template 16 | concept Var = is_var_v; 17 | 18 | [[nodiscard]] constexpr inline auto value(auto value); 19 | 20 | namespace detail { 21 | template 22 | [[nodiscard]] constexpr inline auto make_constant(); 23 | } 24 | 25 | template struct unsafe_cast_ferry { 26 | private: 27 | T v; 28 | 29 | public: 30 | SAFE_INLINE constexpr explicit(true) unsafe_cast_ferry(T new_value) 31 | : v{new_value} {} 32 | 33 | [[nodiscard]] SAFE_INLINE constexpr auto value() const -> T { return v; } 34 | }; 35 | 36 | } // namespace safe 37 | 38 | template 39 | requires(safe::Var) 40 | [[nodiscard]] constexpr auto unsafe_cast(auto const &src) { 41 | return T{safe::unsafe_cast_ferry{src}}; 42 | } 43 | 44 | template 45 | requires(!safe::Var) 46 | [[nodiscard]] constexpr auto unsafe_cast(auto const &src) { 47 | return src; 48 | } 49 | -------------------------------------------------------------------------------- /test/safe/dsl/minus.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using ::testing::_; 7 | using ::testing::InSequence; 8 | using ::testing::Return; 9 | 10 | using namespace safe; 11 | using namespace safe::literals; 12 | 13 | TEST(safe_dsl_minus, sub_two_ival_constants) { 14 | EXPECT_EQ((ival<30, 30> - ival<12, 12>), (ival<18, 18>)); 15 | } 16 | 17 | TEST(safe_dsl_minus, sub_two_intervals) { 18 | EXPECT_EQ((ival<10, 20> - ival<40, 80>), (ival<-70, -20>)); 19 | } 20 | 21 | TEST(safe_dsl_minus, sub_two_mask_constants) { 22 | EXPECT_EQ((mask<0, 12> - mask<0, 8>), (mask<0, 4>)); 23 | } 24 | 25 | // TEST(safe_dsl_minus, sub_two_masks_1) { 26 | // auto const actual = dsl::detail::simp(mask<0x3> - mask<0x3>); 27 | 28 | // EXPECT_EQ( 29 | // actual.var_bits, 30 | // 0xffff'ffff 31 | // ); 32 | // } 33 | 34 | // TEST(safe_dsl_minus, sub_two_masks_2) { 35 | // EXPECT_EQ( 36 | // (mask<0xf> - mask<0xff>), 37 | // (mask<0xffff'ffff>) 38 | // ); 39 | // } 40 | 41 | // TEST(safe_dsl_minus, sub_two_masks_3) { 42 | // EXPECT_EQ( 43 | // (mask<0, 13> - mask<0, 29>), 44 | // (mask<0, -16>) 45 | // ); 46 | // } 47 | 48 | // TEST(safe_dsl_minus, sub_two_masks_4) { 49 | // EXPECT_EQ( 50 | // (mask<0x11> - mask<0x11>), 51 | // (mask<0xffff'ffff>) 52 | // ); 53 | // } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /test/safe/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(array) 2 | add_subdirectory(var) 3 | 4 | function(add_test_suites) 5 | foreach(test_file ${ARGN}) 6 | string(REPLACE "/" "_" test_name "${test_file}") 7 | 8 | add_unit_test( 9 | ${test_name} 10 | GTEST 11 | FILES 12 | ${test_file} 13 | INCLUDE_DIRECTORIES 14 | ${CMAKE_SOURCE_DIR}/test/ 15 | LIBRARIES 16 | safe_arithmetic) 17 | endforeach() 18 | endfunction() 19 | 20 | add_test_suites( 21 | big_integer/detail/storage.cpp 22 | big_integer/detail/plus.cpp 23 | big_integer/detail/minus.cpp 24 | big_integer/detail/negate.cpp 25 | big_integer/detail/bit_and.cpp 26 | big_integer/detail/bit_or.cpp 27 | big_integer/detail/bit_xor.cpp 28 | big_integer/detail/bit_not.cpp 29 | big_integer/detail/shift.cpp 30 | big_integer/detail/multiplies.cpp 31 | big_integer/detail/compare.cpp 32 | big_integer/detail/divides.cpp 33 | big_integer.cpp 34 | var.cpp 35 | match.cpp 36 | array.cpp 37 | dsl/add.cpp 38 | dsl/divide.cpp 39 | dsl/intersection.cpp 40 | dsl/is_equal.cpp 41 | dsl/is_subset.cpp 42 | dsl/abs.cpp 43 | dsl/mask.cpp 44 | dsl/bitwise_and.cpp 45 | dsl/detail/triint.cpp 46 | dsl/bitwise_or.cpp 47 | dsl/bitwise_xor.cpp 48 | dsl/bitwise_invert.cpp 49 | dsl.cpp 50 | dsl/minus.cpp) 51 | -------------------------------------------------------------------------------- /include/safe/detail/concepts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace safe::detail { 6 | template 7 | concept iter_like = requires(I i) { 8 | i++; 9 | *i; 10 | i != i; 11 | }; 12 | 13 | template 14 | concept range_like = requires(R r) { 15 | { r.begin() } -> iter_like; 16 | r.end(); 17 | }; 18 | 19 | template 20 | constexpr bool is_decimal_digit_v = Char >= '0' && Char <= '9'; 21 | 22 | template constexpr bool is_delimiter_v = Char == '\''; 23 | 24 | template 25 | concept decimal_number = 26 | ((is_decimal_digit_v or is_delimiter_v) and ...); 27 | 28 | template 29 | concept decimal_integer = std::integral && decimal_number; 30 | 31 | template constexpr bool is_0_v = Char == '0'; 32 | template constexpr bool is_x_v = Char == 'x'; 33 | 34 | template 35 | constexpr bool is_hex_char_v = 36 | (Char >= 'A' && Char <= 'F' || Char >= 'a' && Char <= 'f'); 37 | 38 | template 39 | constexpr bool is_hex_digit_v = 40 | is_decimal_digit_v || is_hex_char_v || is_delimiter_v; 41 | 42 | template 43 | concept hex_number = 44 | is_0_v && is_x_v && (is_hex_digit_v && ...); 45 | 46 | template 47 | concept hex_integer = 48 | std::unsigned_integral && hex_number; 49 | } // namespace safe::detail 50 | -------------------------------------------------------------------------------- /include/safe/big_integer/detail/algorithms.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace safe::_big_integer::detail { 8 | [[nodiscard]] constexpr auto reverse_zip_transform(auto op) { 9 | return [=](auto &result, auto const &lhs, auto const &rhs) -> void { 10 | for (auto i = result.num_elems; i > std::size_t{};) { 11 | --i; 12 | result.set(i, op(lhs.get(i), rhs.get(i))); 13 | } 14 | }; 15 | } 16 | 17 | [[nodiscard]] constexpr auto zip_transform(auto op) { 18 | return [=](auto &result, auto const &lhs, auto const &rhs) -> void { 19 | for (auto i = std::size_t{}; i < result.num_elems; i++) { 20 | result.set(i, op(lhs.get(i), rhs.get(i))); 21 | } 22 | }; 23 | } 24 | 25 | [[nodiscard]] constexpr auto stateful_zip_transform(auto initial_value, 26 | auto op) { 27 | return [=](auto &result, auto const &lhs, auto const &rhs) -> void { 28 | auto state = initial_value; 29 | for (auto i = std::size_t{}; i < result.num_elems; i++) { 30 | result.set(i, op(state, lhs.get(i), rhs.get(i))); 31 | } 32 | }; 33 | } 34 | 35 | [[nodiscard]] constexpr auto transform(auto op) { 36 | return [=](auto &result, auto const &value) -> void { 37 | for (auto i = std::size_t{}; i < result.num_elems; i++) { 38 | result.set(i, op(value.get(i))); 39 | } 40 | }; 41 | } 42 | } // namespace safe::_big_integer::detail 43 | -------------------------------------------------------------------------------- /include/safe/big_integer/detail/compare.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace safe::_big_integer::detail { 10 | template 11 | [[nodiscard]] constexpr auto unsigned_compare(storage const &lhs, 12 | storage const &rhs) 13 | -> std::strong_ordering { 14 | for (auto i = std::max(lhs.num_elems, rhs.num_elems); i > std::size_t{};) { 15 | --i; 16 | auto const l = lhs.get(i); 17 | auto const r = rhs.get(i); 18 | if (l < r) { 19 | return std::strong_ordering::less; 20 | } 21 | if (l > r) { 22 | return std::strong_ordering::greater; 23 | } 24 | } 25 | return std::strong_ordering::equal; 26 | } 27 | 28 | template 29 | [[nodiscard]] constexpr auto operator<=>(storage const &lhs, 30 | storage const &rhs) 31 | -> std::strong_ordering { 32 | if (lhs.negative()) { 33 | if (rhs.negative()) { 34 | return unsigned_compare(lhs, rhs); 35 | } 36 | return std::strong_ordering::less; 37 | } 38 | if (rhs.negative()) { 39 | return std::strong_ordering::greater; 40 | } 41 | return unsigned_compare(lhs, rhs); 42 | } 43 | } // namespace safe::_big_integer::detail 44 | -------------------------------------------------------------------------------- /test/safe/big_integer/detail/plus.cpp: -------------------------------------------------------------------------------- 1 | #include "properties.hpp" 2 | #include "storage_gen.hpp" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace safe::_big_integer::detail { 11 | RC_GTEST_PROP(plus, is_commutative, (storage<128> a, storage<128> b)) { 12 | is_commutative(plus, a, b); 13 | } 14 | 15 | RC_GTEST_PROP(plus, asymmetric_is_commutative, 16 | (storage<256> a, storage<128> b)) { 17 | is_commutative(plus, a, b); 18 | } 19 | 20 | RC_GTEST_PROP(plus, identity, (storage<128> a)) { 21 | identity<128, storage<32>{}>(plus, a); 22 | } 23 | 24 | RC_GTEST_PROP(plus, is_associative, 25 | (storage<128> a, storage<128> b, storage<128> c)) { 26 | is_associative>(plus, a, b, c); 27 | } 28 | 29 | RC_GTEST_PROP(plus, negative_self_is_domination, (storage<128> a)) { 30 | storage<128> neg_a{}; 31 | negate(neg_a, a); 32 | storage<128> actual{}; 33 | plus(actual, a, neg_a); 34 | storage<128> zero{}; 35 | RC_ASSERT(actual == zero); 36 | } 37 | 38 | RC_GTEST_PROP(plus, is_plus, (int64_t a, int64_t b)) { 39 | RC_PRE(addition_will_not_overflow(a, b)); 40 | storage<64> actual{}; 41 | plus(actual, make_storage(a), make_storage(b)); 42 | storage<64> expected = make_storage(a + b); 43 | RC_ASSERT(actual == expected); 44 | } 45 | 46 | RC_GTEST_PROP(plus, lhs_can_be_result, (storage<256> a, storage<256> b)) { 47 | lhs_can_be_result>(plus, a, b); 48 | } 49 | } // namespace safe::_big_integer::detail 50 | -------------------------------------------------------------------------------- /include/safe/dsl/detail/eval_is_subset.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace safe::dsl::detail { 9 | template struct all_of { 10 | using type = all_of; 11 | constexpr static bool value = (eval_t::value && ...); 12 | 13 | [[nodiscard]] constexpr explicit operator bool() const { return value; } 14 | }; 15 | 16 | template struct any_of { 17 | using type = any_of; 18 | constexpr static bool value = (eval_t::value || ...); 19 | 20 | [[nodiscard]] constexpr explicit operator bool() const { return value; } 21 | }; 22 | 23 | template 24 | struct eval, RhsT>> 25 | : public all_of>...> {}; 26 | 27 | template 28 | struct eval>> 29 | : public any_of, RhsTs>...> {}; 30 | 31 | template 32 | struct eval, union_t>> 33 | : public all_of>...> {}; 34 | 35 | template 36 | struct eval, 37 | std::enable_if_t && !is_union_v && 38 | (!is_primitive_v || !is_primitive_v)>> 39 | : public eval_t, eval_t>> {}; 40 | } // namespace safe::dsl::detail 41 | -------------------------------------------------------------------------------- /include/safe/detail/assume.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // NOLINTBEGIN(cppcoreguidelines-macro-usage) 4 | 5 | #ifdef SAFE_TESTING 6 | // if testing is turned on, turn assumptions into the appropriate framework's 7 | // assertion 8 | #ifdef RC_ASSERT 9 | // RapidCheck 10 | #define SAFE_ASSUME(expr) RC_ASSERT(expr) 11 | 12 | #elif defined(REQUIRE) 13 | // Catch2 14 | #define SAFE_ASSUME(expr) REQUIRE(expr) 15 | 16 | #elif defined(ASSERT_TRUE) 17 | // GoogleTest 18 | #define SAFE_ASSUME(expr) ASSERT_TRUE(expr) 19 | 20 | #else 21 | #define SAFE_ASSUME(expr) 22 | #endif 23 | #else 24 | // adapted from section 4 in 25 | // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1774r4.pdf 26 | #ifdef __cpp_assume 27 | #define SAFE_ASSUME(expr) [[assume(expr)]] 28 | #elif defined(__clang__) 29 | // https://clang.llvm.org/docs/LanguageExtensions.html#builtin-assume 30 | #define SAFE_ASSUME(expr) __builtin_assume(expr) 31 | #elif defined(__GNUC__) && !defined(__ICC) 32 | #if __GNUC__ >= 13 33 | // https://gcc.gnu.org/onlinedocs/gcc-13.1.0/gcc/Statement-Attributes.html#index-assume-statement-attribute 34 | #define SAFE_ASSUME(expr) __attribute__((assume(expr))) 35 | #else 36 | // this is very questionable whether it does anything at all 37 | #define SAFE_ASSUME(expr) \ 38 | if (expr) { \ 39 | } else { \ 40 | __builtin_unreachable(); \ 41 | } 42 | #endif 43 | #elif defined(_MSC_VER) || defined(__ICC) 44 | #define SAFE_ASSUME(expr) __assume(expr) 45 | #endif 46 | #endif 47 | 48 | // NOLINTEND(cppcoreguidelines-macro-usage) 49 | -------------------------------------------------------------------------------- /include/safe/dsl/shift_left.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace safe::dsl { 8 | template 9 | struct shift_left : public detail::binary_op {}; 10 | 11 | template 12 | struct shift_left, ival_t> 13 | : public detail::binary_op { 14 | using type = ival_t; 18 | }; 19 | 20 | template 21 | requires(rhs_min == rhs_max) 22 | struct shift_left, 23 | ival_t> : public detail::binary_op { 24 | using type = mask_t; 25 | }; 26 | 27 | template 28 | requires(rhs_min < rhs_max) 29 | struct shift_left, 30 | ival_t> : public detail::binary_op { 31 | using lhs_mask = mask_t; 32 | 33 | using type = bitwise_or>, 34 | shift_left>>; 35 | }; 36 | 37 | template 38 | [[nodiscard]] constexpr auto operator<<(LhsT, RhsT) -> shift_left { 39 | return {}; 40 | } 41 | } // namespace safe::dsl -------------------------------------------------------------------------------- /include/safe/dsl/detail/eval_binary_op.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace safe::dsl::detail { 10 | 11 | template 12 | using enable_for_binary_op_t = 13 | typename std::enable_if_t>; 14 | 15 | template