├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── appveyor.yml ├── conanfile.txt ├── include └── st │ ├── is_strong_type.hpp │ ├── st.hpp │ ├── traits.hpp │ ├── type.hpp │ └── unwrap.hpp └── tests └── strong_type-tests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | 7 | # Libraries 8 | *.a 9 | 10 | # Shared objects 11 | *.so 12 | *.so.* 13 | 14 | # Executables 15 | *.out 16 | *.hex 17 | *.bin 18 | 19 | # Build directory 20 | build 21 | 22 | # CLion 23 | .idea 24 | cmake-build-debug 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - os: linux 4 | dist: xenial 5 | language: python 6 | cache: pip 7 | python: 3.7 8 | compiler: gcc 9 | addons: 10 | apt: 11 | sources: ['ubuntu-toolchain-r-test'] 12 | packages: ['g++-8', 'cmake'] 13 | env: 14 | - CXX=g++-8 15 | - CC=gcc-8 16 | - CONAN_COMPILER_VERSION=8 17 | - CONAN_LIBCXX_VERSION=libstdc++11 18 | - PIP=pip 19 | 20 | - os: linux 21 | dist: xenial 22 | language: python 23 | cache: pip 24 | python: 3.7 25 | compiler: clang 26 | addons: 27 | apt: 28 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-8'] 29 | packages: ['clang-8', 'g++-8', 'cmake'] 30 | env: 31 | - CXX=clang++-8 32 | - CC=clang-8 33 | - CONAN_COMPILER_VERSION=8 34 | - CONAN_LIBCXX_VERSION=libstdc++11 35 | - PIP=pip 36 | 37 | - os: osx 38 | osx_image: xcode10.2 39 | compiler: clang 40 | env: 41 | - CXX=clang++ 42 | - CC=clang 43 | - PIP=pip3 44 | 45 | script: 46 | - ${CXX} --version 47 | - cmake --version 48 | - ${PIP} install conan 49 | - conan profile new default --detect 50 | - | 51 | [ -n "${CONAN_COMPILER_VERSION}" ] && conan profile update settings.compiler.version=${CONAN_COMPILER_VERSION} default || true 52 | - | 53 | [ -n "${CONAN_LIBCXX_VERSION}" ] && conan profile update settings.compiler.libcxx=${CONAN_LIBCXX_VERSION} default || true 54 | - conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan 55 | - mkdir build 56 | - cd build 57 | - conan install .. --build missing 58 | - cmake .. -DSTRONG_TYPE_BUILD_TESTS=ON 59 | - make -kj2 60 | - ./bin/strong_type-tests 61 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | 3 | project(strong_type) 4 | 5 | set(STRONG_TYPE_SOURCES 6 | ${CMAKE_CURRENT_SOURCE_DIR}/include/st/is_strong_type.hpp 7 | ${CMAKE_CURRENT_SOURCE_DIR}/include/st/unwrap.hpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/include/st/type.hpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/include/st/traits.hpp 10 | ) 11 | 12 | add_library(strong_type INTERFACE) 13 | 14 | target_sources(strong_type INTERFACE ${STRONG_TYPE_SOURCES}) 15 | 16 | target_include_directories(strong_type INTERFACE include) 17 | 18 | option(STRONG_TYPE_BUILD_TESTS "Build tests of the strong_type library" OFF) 19 | 20 | if (STRONG_TYPE_BUILD_TESTS) 21 | set(CMAKE_CXX_STANDARD 17) 22 | set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wpadded -O3") 23 | include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) 24 | conan_basic_setup() 25 | 26 | add_executable(strong_type-tests 27 | tests/strong_type-tests.cpp 28 | ) 29 | 30 | target_link_libraries(strong_type-tests strong_type ${CONAN_LIBS}) 31 | endif () 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Clément "Doom" Doumergue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # strong_type 2 | C++ implementation of strong types 3 | 4 | | Build Status | | 5 | |------------------------------|------| 6 | | Linux (gcc-8, clang-8) / OSX | [![Build Status](https://travis-ci.com/doom/strong_type.svg?branch=master)](https://travis-ci.com/doom/strong_type) | 7 | 8 | ## Table of contents 9 | 10 | - [Table of contents](#table-of-contents) 11 | - [What is this ?](#what-is-this) 12 | - [A tour of the library](#library-tour) 13 | - [A minimal example](#minimal-example) 14 | - [Adding expressiveness](#adding-expressiveness) 15 | - [Adding strong typing](#adding-strong-typing) 16 | - [Customizing behavior](#customizing-behavior) 17 | - [Examples](#examples) 18 | - [The easy way](#the-easy-way) 19 | - [The customizable way](#the-customizable-way) 20 | - [Built-In traits](#built-ins) 21 | 22 | ## What is this ? 23 | 24 | This tiny library provides a way to wrap an existing type in order to give it additional meaning. 25 | If you are already familiar with the concept of strong types, you can skip directly to the [Examples](#examples) section. 26 | 27 | Otherwise, read along ! 28 | 29 | ## A tour of the library 30 | 31 | #### A minimal example 32 | 33 | Let's take a basic example: we want to represent **a distance** in our code. 34 | 35 | The immediate idea we could have would be to use an integral type, such as `int`: 36 | 37 | ```c++ 38 | int distance_from_a_to_b = 10; 39 | ``` 40 | 41 | However, the type of the variable we work with **does not convey any information** about what its value actually represents. The only thing it tells us is how it is implemented. As a programmer reading the above code, you would need to rely on the name of the variable in order to understand the code. 42 | 43 | #### Adding expressiveness 44 | 45 | This can be easily fixed using a type alias: 46 | 47 | ```c++ 48 | using distance = int; 49 | distance from_a_to_b = 10; 50 | ``` 51 | 52 | That way, our code is **more expressive**, and **easier to read** for humans. 53 | 54 | But we still have an issue ! From the compiler's point of view, the `int` and `distance` types are identical. This can lead to error-prone constructs: 55 | 56 | ```c++ 57 | int definitely_not_a_distance = 10; 58 | distance from_a_to_b = definitely_not_a_distance; // Ouch ! 59 | ``` 60 | 61 | The code above is **not correct**, because it allows converting `definitely_not_a_distance` (which is clearly, *not a distance*) to a `distance` object implicitly. 62 | 63 | This is the first case for which this library can help: it can "hide" the real nature of a type, in order to prevent errors and unwanted conversions. 64 | 65 | #### Adding strong typing 66 | 67 | In order to fully hide the implementation of a type, we use the `st::type` wrapper. It takes two (or more, but wait !) template parameters : **the type to wrap**, and **a tag** to guarantee its uniqueness. 68 | 69 | ```c++ 70 | using distance = st::type; 71 | ``` 72 | 73 | *Note: the tag can be any type, as long as it is only used as tag by a single strong type* 74 | 75 | Now, both the programmer and the compiler can distinguish a `distance` from a regular `int`. 76 | 77 | ```c++ 78 | int definitely_not_a_distance = 10; 79 | int a_distance_value = 10; 80 | // distance from_a_to_b = definitely_not_a_distance; // Not OK, would not compile 81 | distance from_a_to_b = distance(a_distance); // OK 82 | distance copy = from_a_to_b; // OK 83 | ``` 84 | 85 | As shown below, it is also possible to access the internal value of the strong type: 86 | 87 | ```c++ 88 | auto distance_value = from_a_to_b.value(); 89 | ``` 90 | 91 | #### Customizing behavior 92 | 93 | Now that we created a new type that hides its underlying implementation, we also lost access to the operations supported by the underlying type. 94 | 95 | Why is that so ? Well, in our case, the concept represented by `distance` might not support all the operations allowed by the `int` type. For example, while you can add two distances together to make a longer distance, you clearly **cannot** multiply a distance with another distance. However, you can multiply a distance with a regular number, in order to scale it. 96 | 97 | In order to customize the behavior of our strong types, this library uses the concept of **traits**. Traits are features that can be added to a type in order to give it additional behavior. Some basic traits are provided directly by the library (see the [Built-In traits](#built-ins) section), but it is also possible to write your own. 98 | 99 | A strong type can use traits like below: 100 | 101 | ```c++ 102 | using distance = st::type // distances can be scaled by a given factor 105 | >; 106 | ``` 107 | 108 | ## Examples 109 | 110 | This library provides two different ways to define strong types, each with different levels of complexity and flexibility. 111 | 112 | #### The easy way 113 | 114 | This is the preferred way to create a basic strong type. It requires a type tag in order to guarantee the strength of the `using` alias. Custom behavior can only be added through traits. 115 | 116 | ```c++ 117 | using integer = st::type< 118 | int, 119 | struct integer_tag, 120 | st::arithmetic, 121 | st::addable_with 122 | >; 123 | ``` 124 | 125 | #### The customizable way 126 | 127 | This way makes it easier to customize a strong type because it skips the `st::type` intermediate. Therefore, it requires creating a structure manually, which also allows defining custom member functions without having to use traits. However, traits are still available through inheritance. 128 | 129 | ```c++ 130 | struct int_with_a_member : 131 | public st::type_base, 132 | public st::traits::arithmetic 133 | { 134 | using st::type_base::type_base; 135 | 136 | constexpr bool is_zero() const noexcept 137 | { 138 | return value() == 0; 139 | } 140 | }; 141 | ``` 142 | 143 | ## Built-In traits 144 | 145 | The table below describes the built-in traits that can be applied to a given strong type `T`. Unless specified otherwise, these traits just forward the requested operation to the underlying types. 146 | 147 | | Trait | Behavior | 148 | | ------------------------- | ------------------------------------------------------------ | 149 | | `addable` | Two `T` objects can be added to obtain a new `T`. | 150 | | `addable_with` | A `T` object can be added with a `U` object to obtain a new `T`. | 151 | | `subtractable` | A `T` object can be subtracted from another `T` object to obtain a new `T` | 152 | | `subtractable_to` | A `T` object can be subtracted from another `T` object to obtain a new `U`. | 153 | | `multiplicable` | Two `T` objects can be multiplied to obtain a new `T`. | 154 | | `multiplicable_with` | A `T` object can be multiplied with a `U` object to obtain a new `T`. | 155 | | `dividable` | A `T` object can be divided by another `T` object to obtain a new `T`. | 156 | | `dividable_by` | A `T` object can be divided by a `U` object to obtain a new `T`. | 157 | | `dividable_to` | A `T` object can be divided by another `T` object to obtain a new `U`. | 158 | | `modulable` | A `T` object can be moduled from another `T` object to obtain a new `T`. | 159 | | `incrementable` | A `T` object can be pre-incremented and post-incremented. | 160 | | `decrementable` | A `T` object can be pre-decremented and post-decremented. | 161 | | `equality_comparable` | Two `T` objects can be compared for equality (supports `==` and `!=`). | 162 | | `orderable` | Two `T` objects can be ordered (supports `<`, `>`, `<=`, `>=`). | 163 | | `arithmetic` | Shorthand trait for `addable`, `subtractable`, `multiplicable`, `dividable`, `modulable`, `incrementable`, `decrementable`, `equality_comparable` and `orderable`. | 164 | | `bitwise_orable` | Two `T` objects can be bitwise `OR`-ed to obtain a new `T`. | 165 | | `bitwise_orable_with` | A `T` object can be bitwise `OR`-ed with a `U` object to obtain a new `T`. | 166 | | `bitwise_andable` | Two `T` objects can be bitwise `AND`-ed to obtain a new `T`. | 167 | | `bitwise_andable_with` | A `T` object can be bitwise `AND`-ed with a `U` object to obtain a new `T`. | 168 | | `bitwise_xorable` | Two `T` objects can be bitwise `XOR`-ed to obtain a new `T`. | 169 | | `bitwise_xorable_with` | A `T` object can be bitwise `XOR`-ed with a `U` object to obtain a new `T`. | 170 | | `bitwise_negatable` | A `T` object can be bitwise negated (`NOT`) to obtain a new `T`. | 171 | | `bitwise_manipulable` | Shorthand trait for `bitwise_orable`,`bitwise_orable_with`, `bitwise_andable`, `bitwise_andable_with`, `bitwise_xorable`, `bitwise_xorable_with`, `bitwise_negatable` and `bitwise_manipulable`. | 172 | | `hashable` | A `T` object can be hashed using `std::hash` (provided that its underlying type can be hashed using `std::hash`). | 173 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | environment: 4 | matrix: 5 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 6 | platform: x64 7 | GENERATOR: Visual Studio 15 2017 Win64 8 | 9 | init: 10 | - cmake --version 11 | - msbuild /version 12 | 13 | install: 14 | - set PATH=%PATH%;%PYTHON%/Scripts/ 15 | - pip.exe install conan 16 | - conan user 17 | - conan --version 18 | - conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan 19 | 20 | before_build: 21 | - conan install .. --build missing 22 | - mkdir cmake-build-debug 23 | - cd cmake-build-debug 24 | - cmake ../ -G "%GENERATOR%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DSTRONG_TYPE_BUILD_TESTS=ON 25 | 26 | build_script: 27 | - cmake --build . --config Debug 28 | - cd bin 29 | - bin/strong_type-tests.exe 30 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | gtest/1.8.1@bincrafters/stable 3 | 4 | [generators] 5 | cmake 6 | -------------------------------------------------------------------------------- /include/st/is_strong_type.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** Created by doom on 26/10/18. 3 | */ 4 | 5 | #ifndef STRONG_TYPE_IS_STRONG_TYPE_HPP 6 | #define STRONG_TYPE_IS_STRONG_TYPE_HPP 7 | 8 | #include 9 | 10 | namespace st 11 | { 12 | namespace traits 13 | { 14 | template 15 | struct is_strong_type_helper 16 | { 17 | template 18 | static std::true_type test(const type_base &); 19 | 20 | static std::false_type test(...); 21 | 22 | using type = decltype(test(std::declval())); 23 | }; 24 | } 25 | 26 | template 27 | struct is_strong_type : traits::is_strong_type_helper::type 28 | { 29 | }; 30 | 31 | template 32 | inline constexpr const bool is_strong_type_v = is_strong_type::value; 33 | } 34 | #endif /* !STRONG_TYPE_IS_STRONG_TYPE_HPP */ 35 | -------------------------------------------------------------------------------- /include/st/st.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** Created by doom on 26/10/18. 3 | */ 4 | 5 | #ifndef STRONG_TYPE_ST_HPP 6 | #define STRONG_TYPE_ST_HPP 7 | 8 | #include 9 | #include 10 | 11 | #endif /* !STRONG_TYPE_ST_HPP */ 12 | -------------------------------------------------------------------------------- /include/st/traits.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** Created by doom on 26/10/18. 3 | */ 4 | 5 | #ifndef STRONG_TYPE_TRAITS_HPP 6 | #define STRONG_TYPE_TRAITS_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace st 13 | { 14 | namespace traits 15 | { 16 | template 17 | struct addable 18 | { 19 | friend constexpr ReturnT operator+(const T &lhs, const OtherOperandT &rhs) noexcept 20 | { 21 | return ReturnT(lhs.value() + unwrap(rhs)); 22 | } 23 | 24 | template >> 26 | friend constexpr ReturnT operator+(const OtherOperandT &lhs, const T &rhs) noexcept 27 | { 28 | return ReturnT(unwrap(lhs) + rhs.value()); 29 | } 30 | }; 31 | 32 | template 33 | struct subtractable 34 | { 35 | friend constexpr ReturnT operator-(const T &lhs, const OtherOperandT &rhs) noexcept 36 | { 37 | return ReturnT(lhs.value() - unwrap(rhs)); 38 | } 39 | }; 40 | 41 | template 42 | struct multiplicable 43 | { 44 | friend constexpr ReturnT operator*(const T &lhs, const OtherOperandT &other) noexcept 45 | { 46 | return ReturnT(lhs.value() * unwrap(other)); 47 | } 48 | 49 | template >> 51 | friend constexpr ReturnT operator*(const OtherOperandT &other, const T &lhs) noexcept 52 | { 53 | return ReturnT(unwrap(other) * lhs.value()); 54 | } 55 | }; 56 | 57 | template 58 | struct dividable 59 | { 60 | friend constexpr ReturnT operator/(const T &lhs, const OtherOperandT &rhs) noexcept 61 | { 62 | return ReturnT(lhs.value() / unwrap(rhs)); 63 | } 64 | }; 65 | 66 | template 67 | struct modulable 68 | { 69 | friend constexpr ReturnT operator%(const T &lhs, const OtherOperandT &rhs) noexcept 70 | { 71 | return ReturnT(lhs.value() % unwrap(rhs)); 72 | } 73 | }; 74 | 75 | template 76 | struct incrementable 77 | { 78 | friend constexpr T &operator++(T &t) noexcept 79 | { 80 | ++t.value(); 81 | return t; 82 | } 83 | 84 | friend constexpr const T operator++(T &t, int) noexcept 85 | { 86 | T ret(t); 87 | 88 | ++t.value(); 89 | return ret; 90 | } 91 | }; 92 | 93 | template 94 | struct decrementable 95 | { 96 | friend constexpr T &operator--(T &t) noexcept 97 | { 98 | --t.value(); 99 | return t; 100 | } 101 | 102 | friend constexpr const T operator--(T &t, int) noexcept 103 | { 104 | T ret(t); 105 | 106 | --t.value(); 107 | return ret; 108 | } 109 | }; 110 | 111 | template 112 | struct equality_comparable 113 | { 114 | friend constexpr bool operator==(const T &lhs, const OtherOperandT &rhs) noexcept 115 | { 116 | return lhs.value() == unwrap(rhs); 117 | } 118 | 119 | template >> 121 | friend constexpr bool operator==(const OtherOperandT &lhs, const T &rhs) noexcept 122 | { 123 | return unwrap(lhs) == rhs.value(); 124 | } 125 | 126 | friend constexpr bool operator!=(const T &lhs, const OtherOperandT &rhs) noexcept 127 | { 128 | return lhs.value() != unwrap(rhs); 129 | } 130 | 131 | template >> 133 | friend constexpr bool operator!=(const OtherOperandT &lhs, const T &rhs) noexcept 134 | { 135 | return unwrap(lhs) != rhs.value(); 136 | } 137 | }; 138 | 139 | template 140 | struct orderable 141 | { 142 | friend constexpr bool operator<(const T &lhs, const OtherOperandT &rhs) noexcept 143 | { 144 | return lhs.value() < unwrap(rhs); 145 | } 146 | 147 | template >> 149 | friend constexpr bool operator<(const OtherOperandT &lhs, const T &rhs) noexcept 150 | { 151 | return unwrap(lhs) < rhs.value(); 152 | } 153 | 154 | friend constexpr bool operator<=(const T &lhs, const OtherOperandT &rhs) noexcept 155 | { 156 | return lhs.value() <= unwrap(rhs); 157 | } 158 | 159 | template >> 161 | friend constexpr bool operator<=(const OtherOperandT &lhs, const T &rhs) noexcept 162 | { 163 | return unwrap(lhs) <= rhs.value(); 164 | } 165 | 166 | friend constexpr bool operator>(const T &lhs, const OtherOperandT &rhs) noexcept 167 | { 168 | return lhs.value() > unwrap(rhs); 169 | } 170 | 171 | template >> 173 | friend constexpr bool operator>(const OtherOperandT &lhs, const T &rhs) noexcept 174 | { 175 | return unwrap(lhs) > rhs.value(); 176 | } 177 | 178 | friend constexpr bool operator>=(const T &lhs, const OtherOperandT &rhs) noexcept 179 | { 180 | return lhs.value() >= unwrap(rhs); 181 | } 182 | 183 | template >> 185 | friend constexpr bool operator>=(const OtherOperandT &lhs, const T &rhs) noexcept 186 | { 187 | return unwrap(lhs) >= rhs.value(); 188 | } 189 | }; 190 | 191 | template 192 | struct arithmetic : addable, 193 | subtractable, 194 | multiplicable, 195 | dividable, 196 | modulable, 197 | incrementable, 198 | decrementable, 199 | equality_comparable, 200 | orderable 201 | { 202 | }; 203 | 204 | template 205 | struct bitwise_orable 206 | { 207 | friend constexpr ReturnT operator|(const T &lhs, const OtherOperandT &rhs) noexcept 208 | { 209 | return ReturnT(lhs.value() | unwrap(rhs)); 210 | } 211 | 212 | template >> 214 | friend constexpr ReturnT operator|(const OtherOperandT &lhs, const T &rhs) noexcept 215 | { 216 | return ReturnT(unwrap(lhs) | rhs.value()); 217 | } 218 | }; 219 | 220 | template 221 | struct bitwise_andable 222 | { 223 | friend constexpr ReturnT operator&(const T &lhs, const OtherOperandT &rhs) noexcept 224 | { 225 | return ReturnT(lhs.value() & unwrap(rhs)); 226 | } 227 | 228 | template >> 230 | friend constexpr ReturnT operator&(const OtherOperandT &lhs, const T &rhs) noexcept 231 | { 232 | return ReturnT(unwrap(lhs) & rhs.value()); 233 | } 234 | }; 235 | 236 | template 237 | struct bitwise_xorable 238 | { 239 | friend constexpr ReturnT operator^(const T &lhs, const OtherOperandT &rhs) noexcept 240 | { 241 | return ReturnT(lhs.value() ^ unwrap(rhs)); 242 | } 243 | 244 | template >> 246 | friend constexpr ReturnT operator^(const OtherOperandT &lhs, const T &rhs) noexcept 247 | { 248 | return ReturnT(unwrap(lhs) ^ rhs.value()); 249 | } 250 | }; 251 | 252 | template 253 | struct bitwise_negatable 254 | { 255 | friend constexpr ReturnT operator~(const T &lhs) noexcept 256 | { 257 | return ReturnT(~lhs.value()); 258 | } 259 | }; 260 | 261 | template 262 | struct bitwise_manipulable : bitwise_orable, 263 | bitwise_andable, 264 | bitwise_xorable, 265 | bitwise_negatable 266 | { 267 | }; 268 | 269 | template 270 | struct hashable 271 | { 272 | }; 273 | } 274 | 275 | struct addable 276 | { 277 | template 278 | using type = traits::addable; 279 | }; 280 | 281 | template 282 | struct addable_with 283 | { 284 | template 285 | using type = traits::addable; 286 | }; 287 | 288 | struct subtractable 289 | { 290 | template 291 | using type = traits::subtractable; 292 | }; 293 | 294 | template 295 | struct subtractable_to 296 | { 297 | template 298 | using type = traits::subtractable; 299 | }; 300 | 301 | struct multiplicable 302 | { 303 | template 304 | using type = traits::multiplicable; 305 | }; 306 | 307 | template 308 | struct multiplicable_with 309 | { 310 | template 311 | using type = traits::multiplicable; 312 | }; 313 | 314 | struct dividable 315 | { 316 | template 317 | using type = traits::dividable; 318 | }; 319 | 320 | template 321 | struct dividable_by 322 | { 323 | template 324 | using type = traits::dividable; 325 | }; 326 | 327 | template 328 | struct dividable_to 329 | { 330 | template 331 | using type = traits::dividable; 332 | }; 333 | 334 | struct modulable 335 | { 336 | template 337 | using type = traits::modulable; 338 | }; 339 | 340 | struct incrementable 341 | { 342 | template 343 | using type = traits::incrementable; 344 | }; 345 | 346 | struct decrementable 347 | { 348 | template 349 | using type = traits::decrementable; 350 | }; 351 | 352 | struct equality_comparable 353 | { 354 | template 355 | using type = traits::equality_comparable; 356 | }; 357 | 358 | struct orderable 359 | { 360 | template 361 | using type = traits::orderable; 362 | }; 363 | 364 | struct arithmetic 365 | { 366 | template 367 | using type = traits::arithmetic; 368 | }; 369 | 370 | struct bitwise_orable 371 | { 372 | template 373 | using type = traits::bitwise_orable; 374 | }; 375 | 376 | template 377 | struct bitwise_orable_with 378 | { 379 | template 380 | using type = traits::bitwise_orable; 381 | }; 382 | 383 | struct bitwise_andable 384 | { 385 | template 386 | using type = traits::bitwise_andable; 387 | }; 388 | 389 | template 390 | struct bitwise_andable_with 391 | { 392 | template 393 | using type = traits::bitwise_andable; 394 | }; 395 | 396 | struct bitwise_xorable 397 | { 398 | template 399 | using type = traits::bitwise_xorable; 400 | }; 401 | 402 | template 403 | struct bitwise_xorable_with 404 | { 405 | template 406 | using type = traits::bitwise_xorable; 407 | }; 408 | 409 | struct bitwise_negatable 410 | { 411 | template 412 | using type = traits::bitwise_negatable; 413 | }; 414 | 415 | struct bitwise_manipulable 416 | { 417 | template 418 | using type = traits::bitwise_manipulable; 419 | }; 420 | 421 | struct hashable 422 | { 423 | template 424 | using type = traits::hashable; 425 | }; 426 | } 427 | 428 | namespace st::details 429 | { 430 | template 431 | using fst = T; 432 | } 433 | 434 | namespace std 435 | { 436 | template 437 | struct hash, 439 | std::enable_if_t>, st::type>> 440 | >> 441 | { 442 | auto operator()(const st::type &t) const 443 | { 444 | return std::hash()(t.value()); 445 | } 446 | }; 447 | } 448 | 449 | #endif /* !STRONG_TYPE_TRAITS_HPP */ 450 | -------------------------------------------------------------------------------- /include/st/type.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** Created by doom on 09/10/18. 3 | */ 4 | 5 | #ifndef STRONG_TYPE_TYPE_HPP 6 | #define STRONG_TYPE_TYPE_HPP 7 | 8 | #include 9 | #include 10 | 11 | namespace st 12 | { 13 | template 14 | class type_base 15 | { 16 | public: 17 | explicit constexpr type_base() : _t() 18 | { 19 | } 20 | 21 | explicit constexpr type_base(const T &t) : _t(t) 22 | { 23 | } 24 | 25 | explicit constexpr type_base(T &&t) noexcept(std::is_nothrow_move_constructible_v) : _t(std::move(t)) 26 | { 27 | } 28 | 29 | constexpr type_base(const type_base &) = default; 30 | 31 | constexpr type_base(type_base &&) noexcept(std::is_nothrow_move_constructible_v) = default; 32 | 33 | constexpr type_base &operator=(const type_base &) = default; 34 | 35 | constexpr type_base &operator=(type_base &&) noexcept(std::is_nothrow_move_assignable_v) = default; 36 | 37 | constexpr const T &value() const & noexcept 38 | { 39 | return _t; 40 | } 41 | 42 | constexpr T &value() & noexcept 43 | { 44 | return _t; 45 | } 46 | 47 | constexpr const T &&value() const && noexcept 48 | { 49 | return std::move(_t); 50 | } 51 | 52 | constexpr T &&value() && noexcept 53 | { 54 | return std::move(_t); 55 | } 56 | 57 | private: 58 | T _t; 59 | }; 60 | 61 | template 62 | class type : 63 | public Traits::template type> ..., 64 | public type_base 65 | { 66 | public: 67 | using type_base::type_base; 68 | 69 | using value_type = T; 70 | using tag_type = Tag; 71 | }; 72 | } 73 | 74 | #endif /* !STRONG_TYPE_TYPE_HPP */ 75 | -------------------------------------------------------------------------------- /include/st/unwrap.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** Created by doom on 26/10/18. 3 | */ 4 | 5 | #ifndef STRONG_TYPE_UNWRAP_HPP 6 | #define STRONG_TYPE_UNWRAP_HPP 7 | 8 | #include 9 | #include 10 | 11 | namespace st 12 | { 13 | template 14 | constexpr inline auto &&unwrap(T &&t) noexcept 15 | { 16 | if constexpr (is_strong_type_v>>) { 17 | return std::forward(t).value(); 18 | } else { 19 | return std::forward(t); 20 | } 21 | } 22 | } 23 | 24 | #endif /* !STRONG_TYPE_UNWRAP_HPP */ 25 | -------------------------------------------------------------------------------- /tests/strong_type-tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** Created by doom on 26/10/18. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using integer = st::type< 11 | int, 12 | struct integer_tag, 13 | st::arithmetic, 14 | st::addable_with, 15 | st::hashable 16 | >; 17 | 18 | using acceleration = st::type; 19 | 20 | using speed = st::type< 21 | int, 22 | struct speed_tag, 23 | st::addable, 24 | st::subtractable, 25 | st::multiplicable_with, 26 | st::dividable_by, 27 | st::dividable_to 28 | >; 29 | 30 | using position = st::type< 31 | int, 32 | struct position_tag, 33 | st::addable_with, 34 | st::subtractable_to, 35 | st::orderable 36 | >; 37 | 38 | namespace 39 | { 40 | struct with_a_member : 41 | public st::type_base, 42 | public st::traits::arithmetic 43 | { 44 | using st::type_base::type_base; 45 | 46 | constexpr bool is_zero() const noexcept 47 | { 48 | return value() == 0; 49 | } 50 | }; 51 | } 52 | 53 | using name = st::type; 54 | 55 | TEST(strong_type, is_strong_type) 56 | { 57 | static_assert(!st::is_strong_type_v); 58 | static_assert(st::is_strong_type_v); 59 | static_assert(st::is_strong_type_v); 60 | } 61 | 62 | TEST(strong_type, unwrap) 63 | { 64 | static_assert(std::is_same_v())), const int &>); 65 | static_assert(std::is_same_v())), const int &>); 66 | static_assert(std::is_same_v())), int &&>); 67 | static_assert(std::is_same_v())), int &&>); 68 | static_assert(std::is_same_v())), int &&>); 69 | } 70 | 71 | template 72 | static inline constexpr bool check_strong_type_triviality_v = 73 | (std::is_trivially_copy_constructible_v == std::is_trivially_copy_constructible_v) && 74 | (std::is_trivially_move_constructible_v == std::is_trivially_move_constructible_v) && 75 | (std::is_trivially_copy_assignable_v == std::is_trivially_copy_assignable_v) && 76 | (std::is_trivially_move_constructible_v == std::is_trivially_move_constructible_v); 77 | 78 | template 79 | static inline constexpr bool check_strong_type_noexceptness_v = 80 | (std::is_nothrow_copy_constructible_v == std::is_nothrow_copy_constructible_v) && 81 | (std::is_nothrow_move_constructible_v == std::is_nothrow_move_constructible_v) && 82 | (std::is_nothrow_copy_assignable_v == std::is_nothrow_copy_assignable_v) && 83 | (std::is_nothrow_move_constructible_v == std::is_nothrow_move_constructible_v); 84 | 85 | TEST(strong_type, triviality) 86 | { 87 | static_assert(check_strong_type_triviality_v); 88 | static_assert(check_strong_type_triviality_v); 89 | static_assert(check_strong_type_triviality_v); 90 | } 91 | 92 | TEST(strong_type, noexceptness) 93 | { 94 | static_assert(check_strong_type_noexceptness_v); 95 | static_assert(check_strong_type_noexceptness_v); 96 | static_assert(check_strong_type_noexceptness_v); 97 | } 98 | 99 | TEST(strong_type, addable) 100 | { 101 | static_assert((integer(1) + integer(1)).value() == integer(2).value()); 102 | static_assert((integer(1) + 1).value() == integer(2).value()); 103 | static_assert((position(2) + 3).value() == position(5).value()); 104 | } 105 | 106 | TEST(strong_type, substractable) 107 | { 108 | static_assert((integer(1) - integer(1)).value() == integer(0).value()); 109 | static_assert(position(3) - position(1) == 2); 110 | static_assert(position(1) - position(2) == -1); 111 | } 112 | 113 | TEST(strong_type, multiplicable) 114 | { 115 | static_assert((integer(2) * integer(4)).value() == integer(8).value()); 116 | 117 | static_assert((speed(2) * acceleration(3)).value() == speed(6).value()); 118 | static_assert((acceleration(3) * speed(2)).value() == speed(6).value()); 119 | } 120 | 121 | TEST(strong_type, dividable) 122 | { 123 | static_assert((integer(4) / integer(2)).value() == integer(2).value()); 124 | 125 | static_assert((speed(4) / 2).value() == speed(2).value()); 126 | static_assert(std::is_same_v); 127 | static_assert((speed(4) / speed(2)).value() == acceleration(2).value()); 128 | } 129 | 130 | TEST(strong_type, modulable) 131 | { 132 | static_assert((integer(4) % integer(2)).value() == integer(0).value()); 133 | } 134 | 135 | TEST(strong_type, pre_incrementable) 136 | { 137 | constexpr integer i = []() constexpr { 138 | integer ret(3); 139 | 140 | ++ret; 141 | return ret; 142 | }(); 143 | 144 | static_assert(i.value() == integer(4).value()); 145 | } 146 | 147 | TEST(strong_type, post_incrementable) 148 | { 149 | constexpr integer i = []() constexpr { 150 | integer ret(3); 151 | 152 | return ret++; 153 | }(); 154 | static_assert(i.value() == integer(3).value()); 155 | 156 | constexpr integer i2 = []() constexpr { 157 | integer ret(3); 158 | 159 | ret++; 160 | return ret; 161 | }(); 162 | 163 | static_assert(i2.value() == integer(4).value()); 164 | } 165 | 166 | TEST(strong_type, pre_decrementable) 167 | { 168 | constexpr integer i = []() constexpr { 169 | integer ret(4); 170 | 171 | --ret; 172 | return ret; 173 | }(); 174 | 175 | static_assert(i.value() == integer(3).value()); 176 | } 177 | 178 | TEST(strong_type, post_decrementable) 179 | { 180 | constexpr integer i = []() constexpr { 181 | integer ret(4); 182 | 183 | return ret--; 184 | }(); 185 | static_assert(i.value() == integer(4).value()); 186 | 187 | constexpr integer i2 = []() constexpr { 188 | integer ret(4); 189 | 190 | ret--; 191 | return ret; 192 | }(); 193 | 194 | static_assert(i2.value() == integer(3).value()); 195 | } 196 | 197 | TEST(strong_type, equality_comparable) 198 | { 199 | static_assert(integer(1) == integer(1)); 200 | static_assert(integer(2) != integer(1)); 201 | } 202 | 203 | TEST(strong_type, orderable) 204 | { 205 | static_assert(integer(1) <= integer(1)); 206 | static_assert(integer(1) >= integer(1)); 207 | 208 | static_assert(integer(2) > integer(1)); 209 | static_assert(integer(2) >= integer(1)); 210 | static_assert(integer(1) < integer(2)); 211 | static_assert(integer(1) <= integer(2)); 212 | 213 | static_assert(position(1) < position(2)); 214 | } 215 | 216 | TEST(strong_type, custom_members) 217 | { 218 | constexpr with_a_member wam(1); 219 | 220 | static_assert(wam.value() == 1); 221 | static_assert(!wam.is_zero()); 222 | static_assert(with_a_member(1) > with_a_member(-1)); 223 | static_assert((with_a_member(1) + with_a_member(2)) == with_a_member(3)); 224 | static_assert((with_a_member(1) - with_a_member(2)) == with_a_member(-1)); 225 | 226 | constexpr with_a_member wam2 = wam; 227 | static_assert(wam == wam2); 228 | static_assert(wam <= wam2); 229 | static_assert(wam >= wam2); 230 | 231 | with_a_member wam3(1); 232 | with_a_member wam4(2); 233 | wam4 = wam3; 234 | ASSERT_EQ(wam3, wam4); 235 | ++wam4; 236 | ASSERT_EQ(wam4, with_a_member(1) + wam3); 237 | wam4 = std::move(wam3); 238 | ASSERT_EQ(with_a_member(1), wam4); 239 | } 240 | 241 | TEST(strong_type, bitwise_manipulable) 242 | { 243 | using bitwise_number = st::type, st::equality_comparable>; 245 | 246 | static_assert((bitwise_number(1) | bitwise_number(2)) == bitwise_number(3)); 247 | static_assert((bitwise_number(1) | 2) == bitwise_number(3)); 248 | static_assert((bitwise_number(1) & bitwise_number(2)) == bitwise_number(0)); 249 | static_assert(~(~bitwise_number(1)) == bitwise_number(1)); 250 | static_assert((bitwise_number(45) ^ bitwise_number(212) ^ bitwise_number(45)) == bitwise_number(212)); 251 | } 252 | 253 | TEST(strong_type, price_spread) 254 | { 255 | using spread = st::type< 256 | double, 257 | struct spread_tag, 258 | st::arithmetic 259 | >; 260 | 261 | using price = st::type< 262 | double, 263 | struct price_tag, 264 | st::arithmetic, 265 | st::multiplicable_with 266 | >; 267 | 268 | constexpr price mid(120); 269 | constexpr spread sp(0.5); 270 | constexpr price pe = mid * (spread{1.0} - sp); 271 | static_assert(pe == price{60.0}); 272 | } 273 | 274 | TEST(strong_type, hashable) 275 | { 276 | auto hasher = std::hash(); 277 | auto underlying_hasher = std::hash(); 278 | 279 | ASSERT_EQ(underlying_hasher(1), hasher(integer(1))); 280 | } 281 | --------------------------------------------------------------------------------