├── .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 | [](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 |
--------------------------------------------------------------------------------