ampsci
High-precision calculations for one- and two-valence atomic systems
StrongType.hpp
1#pragma once
2#include <iostream>
3#include <type_traits>
4
5namespace qip {
6
7/*!
8 @brief A light-weight easy-to-use single-file header-only template class for
9 strong typing.
10
11 @details
12 It works using scoped enums; the enum value (+scope) are used to ensure
13 uniqueness for a given user-defined strong type. The class template, defined in
14 the qip namespace, takes an enum class value, and a base type. The base type is
15 confined to be an arithmetic type (float, double, int, long, etc.) to allow for
16 operator overloading. The 'using' declaration is optional, but makes things much
17 easier.
18
19 ```cpp
20 using MyType = qip::StrongType<EnumClass::value, BaseType>;
21 ```
22
23 The newly-defined Strong Type will, for the most part, behave just like an
24 instance of the underlying base type would. In particular, the usual arithmetic
25 operations (+, -, \*, /, +=, \*= etc.) are all defined, and they work with
26 iostreams. The main difference is that all implicit conversions are banned.
27
28 ## Examples
29
30 Usage is best shown with examples. Consider this dummy problem, where we define
31 strong types for Energy, Mass, and Velocity.
32
33 ```cpp
34 #include "StrongType.hpp"
35 enum class MechanicsTypes { energy, mass, velocity };
36 // Use of enum class ensures each StrongType is unique
37
38 using Energy = qip::StrongType<MechanicsTypes::energy, double>;
39 using Mass = qip::StrongType<MechanicsTypes::mass, double>;
40 using Velocity = qip::StrongType<MechanicsTypes::velocity, double>;
41```
42*/
43template <auto enumV, typename BaseT>
44struct StrongType {
45private:
46 static_assert(std::is_arithmetic_v<BaseT>,
47 "StrongType only available for arithmetic types");
48 static_assert(
49 std::is_enum_v<decltype(enumV)>,
50 "StrongType must be instantiated with scoped enum (enum class)");
51 using StrongT = StrongType<enumV, BaseT>; // type alias
52
53public:
54 BaseT v;
55
56 explicit constexpr StrongType(BaseT tv) : v(tv) {}
57 explicit constexpr operator BaseT() const { return v; }
58 constexpr BaseT &as_base() { return v; }
59 [[nodiscard]] constexpr BaseT as_base() const { return v; }
60
61 //! makes 'BaseType' publicly accessible
62 using BaseType = BaseT;
63
64 //! Provides operators for regular arithmetic operations
65 constexpr StrongT &operator*=(const StrongT &rhs) {
66 this->v *= rhs.v;
67 return *this;
68 }
69 friend constexpr StrongT operator*(StrongT lhs, const StrongT &rhs) {
70 return lhs *= rhs;
71 }
72 constexpr StrongT &operator/=(const StrongT &rhs) {
73 this->v /= rhs.v;
74 return *this;
75 }
76 friend constexpr StrongT operator/(StrongT lhs, const StrongT &rhs) {
77 return lhs /= rhs;
78 }
79 constexpr StrongT &operator+=(const StrongT &rhs) {
80 this->v += rhs.v;
81 return *this;
82 }
83 friend constexpr StrongT operator+(StrongT lhs, const StrongT &rhs) {
84 return lhs += rhs;
85 }
86 constexpr StrongT &operator-=(const StrongT &rhs) {
87 this->v -= rhs.v;
88 return *this;
89 }
90 friend constexpr StrongT operator-(StrongT lhs, const StrongT &rhs) {
91 return lhs -= rhs;
92 }
93
94 //! Provides Base*Strong and Strong*Base operators for scalar multiplication.
95 constexpr StrongT &operator*=(const BaseT &rhs) {
96 this->v *= rhs;
97 return *this;
98 }
99 friend constexpr StrongT operator*(StrongT lhs, const BaseT &rhs) {
100 return lhs *= rhs;
101 }
102 friend constexpr StrongT operator*(const BaseT &lhs, StrongT rhs) {
103 return rhs *= lhs;
104 }
105 //! Provide Strong/Base, but NOT Base/Strong (still scalar multiplication).
106 // If StrongT is used for physical units, this will likely not be what you
107 // want. In this case, just be explicit. Base/Strong is not scalar
108 // multiplication.
109 constexpr StrongT &operator/=(const BaseT &rhs) {
110 this->v /= rhs;
111 return *this;
112 }
113 friend constexpr StrongT operator/(StrongT lhs, const BaseT &rhs) {
114 return lhs /= rhs;
115 }
116
117 //! Provides pre/post increment/decrement (++, --) operators
118 constexpr StrongT &operator++() {
119 ++v;
120 return *this;
121 }
122 constexpr StrongT operator++(int) {
123 StrongT result(*this);
124 ++(*this);
125 return result;
126 }
127 constexpr StrongT &operator--() {
128 --v;
129 return *this;
130 }
131 constexpr StrongT operator--(int) {
132 StrongT result(*this);
133 --(*this);
134 return result;
135 }
136
137 //! Provides comparison operators
138 friend constexpr bool operator==(const StrongT &lhs, const StrongT &rhs) {
139 return lhs.v == rhs.v;
140 }
141 friend constexpr bool operator!=(const StrongT &lhs, const StrongT &rhs) {
142 return !(lhs == rhs);
143 }
144 friend constexpr bool operator<(const StrongT &lhs, const StrongT &rhs) {
145 return lhs.v < rhs.v;
146 }
147 friend constexpr bool operator>(const StrongT &lhs, const StrongT &rhs) {
148 return rhs < lhs;
149 }
150 friend constexpr bool operator<=(const StrongT &lhs, const StrongT &rhs) {
151 return !(rhs < lhs);
152 }
153 friend constexpr bool operator>=(const StrongT &lhs, const StrongT &rhs) {
154 return !(lhs < rhs);
155 }
156
157 //! Provides comparison operators with BaseT rvalue literals (not lvalues).
158 friend constexpr bool operator==(const StrongT &lhs, const BaseT &&rhs) {
159 return lhs.v == rhs;
160 }
161 friend constexpr bool operator!=(const StrongT &lhs, const BaseT &&rhs) {
162 return lhs.v != rhs;
163 }
164 friend constexpr bool operator<(const StrongT &lhs, const BaseT &&rhs) {
165 return lhs.v < rhs;
166 }
167 friend constexpr bool operator>(const StrongT &lhs, const BaseT &&rhs) {
168 return lhs.v > rhs;
169 }
170 friend constexpr bool operator<=(const StrongT &lhs, const BaseT &&rhs) {
171 return lhs.v <= rhs;
172 }
173 friend constexpr bool operator>=(const StrongT &lhs, const BaseT &&rhs) {
174 return lhs.v >= rhs;
175 }
176 friend constexpr bool operator==(const BaseT &&lhs, const StrongT &rhs) {
177 return lhs == rhs.v;
178 }
179 friend constexpr bool operator!=(const BaseT &&lhs, const StrongT &rhs) {
180 return lhs != rhs.v;
181 }
182 friend constexpr bool operator<(const BaseT &&lhs, const StrongT &rhs) {
183 return lhs < rhs.v;
184 }
185 friend constexpr bool operator>(const BaseT &&lhs, const StrongT &rhs) {
186 return lhs > rhs.v;
187 }
188 friend constexpr bool operator<=(const BaseT &&lhs, const StrongT &rhs) {
189 return lhs <= rhs.v;
190 }
191 friend constexpr bool operator>=(const BaseT &&lhs, const StrongT &rhs) {
192 return lhs >= rhs.v;
193 }
194
195 //! Provides iostream interface, works as it would for BaseT
196 friend std::ostream &operator<<(std::ostream &os, const StrongT &rhs) {
197 return os << rhs.v;
198 }
199 friend std::istream &operator>>(std::istream &is, StrongT &rhs) {
200 return is >> rhs.v;
201 }
202};
203
204} // namespace qip
General-purpose utility library.
Definition Array.hpp:23
A light-weight easy-to-use single-file header-only template class for strong typing.
Definition StrongType.hpp:44
constexpr StrongT & operator*=(const BaseT &rhs)
Provides Base*Strong and Strong*Base operators for scalar multiplication.
Definition StrongType.hpp:95
constexpr StrongT & operator/=(const BaseT &rhs)
Provide Strong/Base, but NOT Base/Strong (still scalar multiplication).
Definition StrongType.hpp:109
constexpr StrongT & operator++()
Provides pre/post increment/decrement (++, –) operators.
Definition StrongType.hpp:118
constexpr StrongT & operator*=(const StrongT &rhs)
Provides operators for regular arithmetic operations.
Definition StrongType.hpp:65
friend std::ostream & operator<<(std::ostream &os, const StrongT &rhs)
Provides iostream interface, works as it would for BaseT.
Definition StrongType.hpp:196
friend constexpr bool operator==(const StrongT &lhs, const BaseT &&rhs)
Provides comparison operators with BaseT rvalue literals (not lvalues).
Definition StrongType.hpp:158
friend constexpr bool operator==(const StrongT &lhs, const StrongT &rhs)
Provides comparison operators.
Definition StrongType.hpp:138
BaseT BaseType
makes 'BaseType' publicly accessible
Definition StrongType.hpp:62