1 // Copyright (C) 2016-2020 Jonathan Müller <jonathanmueller.dev@gmail.com>
2 // This file is subject to the license terms in the LICENSE file
3 // found in the top-level directory of this distribution.
4
5 #ifndef TYPE_SAFE_BOUNDED_TYPE_HPP_INCLUDED
6 #define TYPE_SAFE_BOUNDED_TYPE_HPP_INCLUDED
7
8 #include <limits>
9 #include <type_traits>
10
11 #include <type_safe/constrained_type.hpp>
12 #include <type_safe/detail/constant_parser.hpp>
13
14 namespace type_safe
15 {
16 namespace constraints
17 {
18 /// Tag type to enable a dynamic bound.
19 struct dynamic_bound
20 {};
21
22 /// \exclude
23 namespace detail
24 {
25 // Base to enable empty base optimization when Bound is not dynamic_bound.
26 // Necessary when T is not a class.
27 template <typename T>
28 struct wrapper
29 {
30 using value_type = T;
31 T value;
32 };
33
34 template <typename Bound>
35 struct is_dynamic : std::is_same<Bound, dynamic_bound>
36 {};
37
38 template <bool Cond, typename T, typename Bound>
39 struct select_bound;
40
41 template <typename T, typename Bound>
42 struct select_bound<true, T, Bound>
43 {
44 using type = wrapper<T>;
45 };
46
47 template <typename T, typename Bound>
48 struct select_bound<false, T, Bound>
49 {
50 static_assert(
51 std::is_convertible<decltype(std::declval<const T&>() < Bound::value), bool>::value,
52 "static bound has wrong type");
53 using type = Bound;
54 };
55
56 template <typename T, typename Bound>
57 using base = typename select_bound<is_dynamic<Bound>::value, T, Bound>::type;
58 } // namespace detail
59
60 // clang-format off
61 /// \exclude
62 #define TYPE_SAFE_DETAIL_MAKE(Name, Op) \
63 template <typename T, typename Bound = dynamic_bound> \
64 class Name : detail::base<T, Bound> \
65 { \
66 static constexpr bool is_dynamic = detail::is_dynamic<Bound>::value; \
67 \
68 using base = detail::base<T, Bound>; \
69 using arg_type = typename std::conditional<is_dynamic, T, Bound>::type; \
70 \
71 public: \
72 using value_type = decltype(base::value); \
73 using bound_type = Bound; \
74 \
75 /** Initializes it with a static bound.
76 * \effects Does nothing, a static bound is not stored.
77 * It will use `Bound::value` as the bound.
78 * \notes This constructor only participates in overload resolution,
79 * if a static bound is used, i.e. `Bound` is not [ts::constraints::dynamic_bound]().
80 * \param Condition
81 * \exclude
82 * \param 1
83 * \exclude */ \
84 template <bool Condition = !is_dynamic, \
85 typename = typename std::enable_if<Condition>::type> \
86 constexpr Name(Bound = {}) \
87 { \
88 } \
89 \
90 /** Initializes it with a dynamic bound.
91 * \effects Copies (1)/moves (2) the object and uses that as bound.
92 * \notes These constructors only participate in overload resolution,
93 * if a dynamic bound is used, i.e. `Bound` is [ts::constraints::dynamic_bound]().
94 * \group dynamic_ctor
95 * \param Condition
96 * \exclude
97 * \param 1
98 * \exclude */ \
99 template <bool Condition = is_dynamic, \
100 typename = typename std::enable_if<Condition>::type> \
101 explicit constexpr Name(const T& bound) : base{bound} \
102 { \
103 } \
104 \
105 /** \group dynamic_ctor
106 * \param Condition
107 * \exclude
108 * \param 1
109 * \exclude */ \
110 template <bool Condition = is_dynamic, \
111 typename = typename std::enable_if<Condition>::type> \
112 explicit constexpr Name(T&& bound) noexcept(std::is_nothrow_move_constructible<T>::value) \
113 : base{std::move(bound)} \
114 { \
115 } \
116 \
117 /** Does the actual bounds check.*/ \
118 template <typename U> \
119 constexpr bool operator()(const U& u) const \
120 { \
121 return u Op get_bound(); \
122 } \
123 \
124 /** \returns The bound.*/ \
125 constexpr const value_type& get_bound() const noexcept \
126 { \
127 return base::value; \
128 } \
129 };
130 // clang-format on
131
132 /// A `Constraint` for the [ts::constrained_type]().
133 ///
134 /// A value is valid if it is less than some given value.
135 TYPE_SAFE_DETAIL_MAKE(less, <)
136
137 /// A `Constraint` for the [ts::constrained_type]().
138 ///
139 /// A value is valid if it is less than or equal to some given value.
140 TYPE_SAFE_DETAIL_MAKE(less_equal, <=)
141
142 /// A `Constraint` for the [ts::constrained_type]().
143 ///
144 /// A value is valid if it is greater than some given value.
145 TYPE_SAFE_DETAIL_MAKE(greater, >)
146
147 /// A `Constraint` for the [ts::constrained_type]().
148 ///
149 /// A value is valid if it is greater than or equal to some given value.
150 TYPE_SAFE_DETAIL_MAKE(greater_equal, >=)
151
152 #undef TYPE_SAFE_DETAIL_MAKE
153
154 /// \exclude
155 namespace detail
156 {
157 // checks that that the value is less than the upper bound
158 template <bool Inclusive, typename T, typename Bound>
159 using upper_bound_t =
160 typename std::conditional<Inclusive, less_equal<T, Bound>, less<T, Bound>>::type;
161
162 // checks that the value is greater than the lower bound
163 template <bool Inclusive, typename T, typename Bound>
164 using lower_bound_t =
165 typename std::conditional<Inclusive, greater_equal<T, Bound>, greater<T, Bound>>::type;
166 } // namespace detail
167
168 /// Tag objects to specify bounds for [ts::constraints::bounded]().
169 /// \group open_closed Open/Closed Tags
170 constexpr bool open = false;
171 /// \group open_closed
172 constexpr bool closed = true;
173
174 /// A `Constraint` for the [ts::constrained_type]().
175 ///
176 /// A value is valid if it is between two given bounds,
177 /// `LowerInclusive`/`UpperInclusive` control whether the lower/upper bound itself is valid too.
178 template <typename T, bool LowerInclusive, bool UpperInclusive,
179 typename LowerBound = dynamic_bound, typename UpperBound = dynamic_bound>
180 class bounded : detail::lower_bound_t<LowerInclusive, T, LowerBound>,
181 detail::upper_bound_t<UpperInclusive, T, UpperBound>
182 {
183 static constexpr bool lower_is_dynamic = detail::is_dynamic<LowerBound>::value;
184 static constexpr bool upper_is_dynamic = detail::is_dynamic<UpperBound>::value;
185
186 using lower_type = detail::lower_bound_t<LowerInclusive, T, LowerBound>;
187 using upper_type = detail::upper_bound_t<UpperInclusive, T, UpperBound>;
188
lower() const189 constexpr const lower_type& lower() const noexcept
190 {
191 return static_cast<const lower_type&>(*this);
192 }
upper() const193 constexpr const upper_type& upper() const noexcept
194 {
195 return static_cast<const upper_type&>(*this);
196 }
197
198 template <typename U>
199 using decay_same = std::is_same<typename std::decay<U>::type, T>;
200
201 public:
202 using value_type = T;
203 using lower_bound = LowerBound;
204 using upper_bound = UpperBound;
205
206 static constexpr auto lower_inclusive = LowerInclusive;
207 static constexpr auto upper_inclusive = UpperInclusive;
208
209 /// Initializes it with static bounds.
210 /// \effects Does nothing, a static bound is not stored.
211 /// It will use `LowerBound::value` as lower bound and `UpperBound::value` as upper bound.
212 /// \notes This constructor does not participate in overload resolution,
213 /// unless both bounds are static,
214 /// i.e. not [ts::constraints::dynamic_bound]().
215 /// \param Condition
216 /// \exclude
217 /// \param 1
218 /// \exclude
219 template <bool Condition = !lower_is_dynamic && !upper_is_dynamic,
220 typename = typename std::enable_if<Condition>::type>
bounded()221 constexpr bounded()
222 {}
223
224 /// Initializes it with (mixed) dynamic bounds.
225 /// \effects Perfectly forwards the arguments to the bounds.
226 /// If a bound is static, the static member `value` will be used as bound,
227 /// if it is dynamic, a copy created by perfectly forwarding will be stored and used as
228 /// bound. \notes This constructor does not participate in overload resolution, unless the
229 /// arguments are convertible to the bounds. \param 2 \exclude \param 3 \exclude
230 template <typename U1, typename U2>
231 explicit constexpr bounded(U1&& lower, U2&& upper,
232 decltype(lower_type(std::forward<U1>(lower)), 0) = 0,
233 decltype(upper_type(std::forward<U2>(upper)), 0) = 0)
234 : lower_type(std::forward<U1>(lower)), upper_type(std::forward<U2>(upper))
235 {}
236
237 /// Does the bounds check.
238 template <typename U>
operator ()(const U & u) const239 constexpr bool operator()(const U& u) const
240 {
241 return lower()(u) && upper()(u);
242 }
243
244 /// \returns The value of the lower bound.
get_lower_bound() const245 constexpr const typename lower_type::value_type& get_lower_bound() const noexcept
246 {
247 return lower().get_bound();
248 }
249
250 /// \returns The value of the upper bound.
get_upper_bound() const251 constexpr const typename upper_type::value_type& get_upper_bound() const noexcept
252 {
253 return upper().get_bound();
254 }
255 };
256
257 /// A `Constraint` for the [ts::constrained_type]().
258 ///
259 /// A value is valid if it is between two given bounds but not the bounds themselves.
260 template <typename T, typename LowerBound = dynamic_bound, typename UpperBound = dynamic_bound>
261 using open_interval = bounded<T, open, open, LowerBound, UpperBound>;
262
263 /// A `Constraint` for the [ts::constrained_type]().
264 ///
265 /// A value is valid if it is between two given bounds or the bounds themselves.
266 template <typename T, typename LowerBound = dynamic_bound, typename UpperBound = dynamic_bound>
267 using closed_interval = bounded<T, closed, closed, LowerBound, UpperBound>;
268 } // namespace constraints
269
270 /// \exclude
271 namespace detail
272 {
273 template <typename T>
274 struct valid_bound : std::integral_constant<bool, true || T::value>
275 {};
276
277 template <class Verifier, bool LowerInclusive, bool UpperInclusive, typename T, typename U1,
278 typename U2>
279 struct bounded_type_impl
280 {
281 static_assert(valid_bound<U1>::value && valid_bound<U2>::value,
282 "make_bounded() called with mismatched types");
283
284 using value_type = T;
285 using lower_constant = U1;
286 using upper_constant = U2;
287
288 using type
289 = constrained_type<value_type,
290 constraints::bounded<value_type, LowerInclusive, UpperInclusive,
291 lower_constant, upper_constant>,
292 Verifier>;
293 };
294
295 template <class Verifier, bool LowerInclusive, bool UpperInclusive, typename T>
296 struct bounded_type_impl<Verifier, LowerInclusive, UpperInclusive, T, T, T>
297 {
298 using value_type = T;
299 using lower_constant = constraints::dynamic_bound;
300 using upper_constant = constraints::dynamic_bound;
301
302 using type
303 = constrained_type<value_type,
304 constraints::bounded<value_type, LowerInclusive, UpperInclusive,
305 lower_constant, upper_constant>,
306 Verifier>;
307 };
308
309 template <class Verifier, bool LowerInclusive, bool UpperInclusive, typename T,
310 typename UpperBound>
311 struct bounded_type_impl<Verifier, LowerInclusive, UpperInclusive, T, T, UpperBound>
312 {
313 static_assert(valid_bound<UpperBound>::value,
314 "make_bounded() called with mismatched types");
315
316 using value_type = T;
317 using lower_constant = constraints::dynamic_bound;
318 using upper_constant = UpperBound;
319
320 using type
321 = constrained_type<value_type,
322 constraints::bounded<value_type, LowerInclusive, UpperInclusive,
323 lower_constant, upper_constant>,
324 Verifier>;
325 };
326
327 template <class Verifier, bool LowerInclusive, bool UpperInclusive, typename T,
328 typename LowerBound>
329 struct bounded_type_impl<Verifier, LowerInclusive, UpperInclusive, T, LowerBound, T>
330 {
331 static_assert(valid_bound<LowerBound>::value,
332 "make_bounded() called with mismatched types");
333
334 using value_type = T;
335 using lower_constant = LowerBound;
336 using upper_constant = constraints::dynamic_bound;
337
338 using type
339 = constrained_type<value_type,
340 constraints::bounded<value_type, LowerInclusive, UpperInclusive,
341 lower_constant, upper_constant>,
342 Verifier>;
343 };
344
345 template <class Verifier, bool LowerInclusive, bool UpperInclusive, typename T, typename U1,
346 typename U2>
347 using make_bounded_type =
348 typename bounded_type_impl<Verifier, LowerInclusive, UpperInclusive,
349 typename std::decay<T>::type, typename std::decay<U1>::type,
350 typename std::decay<U2>::type>::type;
351 } // namespace detail
352
353 /// An alias for [ts::constrained_type]() that uses [ts::constraints::bounded]() as its
354 /// `Constraint`. \notes This is some type where the values must be in a certain interval.
355 template <typename T, bool LowerInclusive, bool UpperInclusive,
356 typename LowerBound = constraints::dynamic_bound,
357 typename UpperBound = constraints::dynamic_bound, typename Verifier = assertion_verifier>
358 using bounded_type = constrained_type<
359 T, constraints::bounded<T, LowerInclusive, UpperInclusive, LowerBound, UpperBound>, Verifier>;
360
361 inline namespace literals
362 {
363 /// \exclude
364 namespace lit_detail
365 {
366 template <typename T, T Value>
367 struct integer_bound
368 {
369 static constexpr T value = Value;
370 };
371
372 template <typename T, T Value>
373 constexpr T integer_bound<T, Value>::value;
374
375 template <typename T, T Value>
operator -(integer_bound<T,Value>)376 constexpr auto operator-(integer_bound<T, Value>) noexcept
377 -> integer_bound<T, Value * T(-1)>
378 {
379 static_assert(std::is_signed<T>::value, "must be a signed integer type");
380 return {};
381 }
382 } // namespace lit_detail
383
384 /// Creates a static bound for [ts::bounded_type]().
385 ///
386 /// This is a bound encapsulated in the type, so there is no overhead.
387 /// You can use it for example like this `ts::make_bounded(50, 0_bound, 100_bound)`,
388 /// to bound an integer between `0` and `100`.
389 /// \returns A type representing the given value,
390 /// the value has type `long long` (1)/`unsigned long long`(2).
391 /// \group bound_lit
392 template <char... Digits>
operator ""_bound()393 constexpr auto operator"" _bound()
394 -> lit_detail::integer_bound<long long, detail::parse<long long, Digits...>()>
395 {
396 return {};
397 }
398
399 /// \group bound_lit
400 template <char... Digits>
operator ""_boundu()401 constexpr auto operator"" _boundu()
402 -> lit_detail::integer_bound<unsigned long long,
403 detail::parse<unsigned long long, Digits...>()>
404 {
405 return {};
406 }
407 } // namespace literals
408
409 /// Creates a [ts::bounded_type]() to a specified [ts::constraints::closed_interval]().
410 /// \returns A [ts::bounded_type]() with the given `value` and lower and upper bounds,
411 /// where the bounds are valid values as well.
412 /// \requires As it uses [ts::assertion_verifier](),
413 /// the value must be valid.
414 /// \notes If this function is passed in dynamic values of the same type as `value`,
415 /// it will create a dynamic bound.
416 /// Otherwise it must be passed static bounds.
417 template <typename T, typename U1, typename U2>
make_bounded(T && value,U1 && lower,U2 && upper)418 constexpr auto make_bounded(T&& value, U1&& lower, U2&& upper)
419 -> detail::make_bounded_type<assertion_verifier, true, true, T, U1, U2>
420 {
421 using result_type = detail::make_bounded_type<assertion_verifier, true, true, T, U1, U2>;
422 return result_type(std::forward<T>(value),
423 typename result_type::constraint_predicate(std::forward<U1>(lower),
424 std::forward<U2>(upper)));
425 }
426
427 /// Creates a [ts::bounded_type]() to a specified [ts::constraints::closed_interval]().
428 /// \returns A [ts::bounded_type]() with the given `value` and lower and upper bounds,
429 /// where the bounds are valid values as well.
430 /// \throws A [ts::constrain_error]() if the `value` isn't valid,
431 /// or anything else thrown by the constructor.
432 /// \notes This is meant for sanitizing user input,
433 /// using a recoverable error handling strategy.
434 /// \notes If this function is passed in dynamic values of the same type as `value`,
435 /// it will create a dynamic bound.
436 /// Otherwise it must be passed static bounds.
437 template <typename T, typename U1, typename U2>
sanitize_bounded(T && value,U1 && lower,U2 && upper)438 constexpr auto sanitize_bounded(T&& value, U1&& lower, U2&& upper)
439 -> detail::make_bounded_type<throwing_verifier, true, true, T, U1, U2>
440 {
441 using result_type = detail::make_bounded_type<throwing_verifier, true, true, T, U1, U2>;
442 return result_type(std::forward<T>(value),
443 typename result_type::constraint_predicate(std::forward<U1>(lower),
444 std::forward<U2>(upper)));
445 }
446
447 /// Creates a [ts::bounded_type]() to a specified [ts::constraints::open_interval]().
448 /// \returns A [ts::bounded_type]() with the given `value` and lower and upper bounds,
449 /// where the bounds are not valid values.
450 /// \requires As it uses [ts::assertion_verifier](),
451 /// the value must be valid.
452 /// \notes If this function is passed in dynamic values of the same type as `value`,
453 /// it will create a dynamic bound.
454 /// Otherwise it must be passed static bounds.
455 template <typename T, typename U1, typename U2>
make_bounded_exclusive(T && value,U1 && lower,U2 && upper)456 constexpr auto make_bounded_exclusive(T&& value, U1&& lower, U2&& upper)
457 -> detail::make_bounded_type<assertion_verifier, false, false, T, U1, U2>
458 {
459 using result_type = detail::make_bounded_type<assertion_verifier, false, false, T, U1, U2>;
460 return result_type(std::forward<T>(value),
461 typename result_type::constraint_predicate(std::forward<U1>(lower),
462 std::forward<U2>(upper)));
463 }
464
465 /// Creates a [ts::bounded_type]() to a specified [ts::constraints::open_interval](),
466 /// using [ts::throwing_verifier]().
467 /// \returns A [ts::bounded_type]() with the given `value` and lower and upper bounds,
468 /// where the bounds are not valid values.
469 /// \throws A [ts::constrain_error]() if the `value` isn't valid,
470 /// or anything else thrown by the constructor.
471 /// \notes This is meant for sanitizing user input,
472 /// using a recoverable error handling strategy.
473 /// \notes If this function is passed in dynamic values of the same type as `value`,
474 /// it will create a dynamic bound.
475 /// Otherwise it must be passed static bounds.
476 template <typename T, typename U1, typename U2>
sanitize_bounded_exclusive(T && value,U1 && lower,U2 && upper)477 constexpr auto sanitize_bounded_exclusive(T&& value, U1&& lower, U2&& upper)
478 -> detail::make_bounded_type<throwing_verifier, false, false, T, U1, U2>
479 {
480 using result_type = detail::make_bounded_type<throwing_verifier, false, false, T, U1, U2>;
481 return result_type(std::forward<T>(value),
482 typename result_type::constraint_predicate(std::forward<U1>(lower),
483 std::forward<U2>(upper)));
484 }
485
486 /// Returns a copy of `val` so that it is in the given [ts::constraints::closed_interval]().
487 /// \effects If it is not in the interval, returns the bound that is closer to the value.
488 /// \output_section clamped_type
489 template <typename T, typename LowerBound, typename UpperBound, typename U>
clamp(const constraints::closed_interval<T,LowerBound,UpperBound> & interval,U && val)490 constexpr auto clamp(const constraints::closed_interval<T, LowerBound, UpperBound>& interval,
491 U&& val) -> typename std::decay<U>::type
492 {
493 return val < interval.get_lower_bound()
494 ? static_cast<typename std::decay<U>::type>(interval.get_lower_bound())
495 : (val > interval.get_upper_bound()
496 ? static_cast<typename std::decay<U>::type>(interval.get_upper_bound())
497 : std::forward<U>(val));
498 }
499
500 /// A `Verifier` for [ts::constrained_type]() that clamps the value to make it valid.
501 ///
502 /// It must be used together with [ts::constraints::less_equal](),
503 /// [ts::constraints::greater_equal]() or [ts::constraints::closed_interval]().
504 struct clamping_verifier
505 {
506 /// \returns If `val` is greater than the bound of `p`,
507 /// returns the bound.
508 /// Otherwise returns the value unchanged
509 template <typename Value, typename T, typename Bound>
verifytype_safe::clamping_verifier510 static constexpr auto verify(Value&& val, const constraints::less_equal<T, Bound>& p) ->
511 typename std::decay<Value>::type
512 {
513 return p(val) ? std::forward<Value>(val)
514 : static_cast<typename std::decay<Value>::type>(p.get_bound());
515 }
516
517 /// \returns If `val` is less than the bound of `p`,
518 /// returns the bound.
519 /// Otherwise returns the value unchanged
520 template <typename Value, typename T, typename Bound>
verifytype_safe::clamping_verifier521 static constexpr auto verify(Value&& val, const constraints::greater_equal<T, Bound>& p) ->
522 typename std::decay<Value>::type
523 {
524 return p(val) ? std::forward<Value>(val)
525 : static_cast<typename std::decay<Value>::type>(p.get_bound());
526 }
527
528 /// \returns Same as `clamp(interval, val)`.
529 template <typename Value, typename T, typename LowerBound, typename UpperBound>
verifytype_safe::clamping_verifier530 static constexpr auto verify(
531 Value&& val, const constraints::closed_interval<T, LowerBound, UpperBound>& interval) ->
532 typename std::decay<Value>::type
533 {
534 return clamp(interval, std::forward<Value>(val));
535 }
536 };
537
538 /// An alias for [ts::constrained_type]() that uses [ts::constraints::closed_interval]() as its
539 /// `Constraint` and [ts::clamping_verifier]() as its `Verifier`. \notes This is some type where the
540 /// values are always clamped so that they are in a certain interval.
541 template <typename T, typename LowerBound = constraints::dynamic_bound,
542 typename UpperBound = constraints::dynamic_bound>
543 using clamped_type = constrained_type<T, constraints::closed_interval<T, LowerBound, UpperBound>,
544 clamping_verifier>;
545
546 /// Creates a [ts::clamped_type]() from the specified [ts::constraints::closed_interval]().
547 /// \returns A [ts::clamped_type]() with the given `value` and lower and upper bounds,
548 /// where the bounds are valid values.
549 /// \notes If this function is passed in dynamic values of the same type as `value`,
550 /// it will create a dynamic bound.
551 /// Otherwise it must be passed static bounds.
552 template <typename T, typename U1, typename U2>
make_clamped(T && value,U1 && lower,U2 && upper)553 constexpr auto make_clamped(T&& value, U1&& lower, U2&& upper)
554 -> detail::make_bounded_type<clamping_verifier, true, true, T, U1, U2>
555 {
556 using result_type = detail::make_bounded_type<clamping_verifier, true, true, T, U1, U2>;
557 return result_type(std::forward<T>(value),
558 typename result_type::constraint_predicate(std::forward<U1>(lower),
559 std::forward<U2>(upper)));
560 }
561 } // namespace type_safe
562
563 #endif // TYPE_SAFE_BOUNDED_TYPE_HPP_INCLUDED
564