userver: userver/utils/numeric_cast.hpp Source File
Loading...
Searching...
No Matches
numeric_cast.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/numeric_cast.hpp
4/// @brief @copybrief utils::numeric_cast
5
6#include <cmath>
7#include <limits>
8#include <stdexcept>
9
10#include <fmt/format.h>
11
12#include <userver/compiler/demangle.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace utils {
17
18namespace impl {
19
20template <typename T>
21using PrintableValue = std::conditional_t<(sizeof(T) > 1), T, int>;
22
23}
24
25/// Detects loss of range when a numeric type is converted, and throws an
26/// exception if the range cannot be preserved
27///
28/// ## Example usage:
29///
30/// @snippet utils/numeric_cast_test.cpp Sample utils::numeric_cast usage
31template <typename To, typename Exception = std::runtime_error, typename From>
32constexpr To numeric_cast(From input) { // NOLINT(readability-identifier-naming)
33 constexpr bool is_integral = std::is_integral_v<From> && std::is_integral_v<To>;
34 constexpr bool is_floating_point = std::is_floating_point_v<From> && std::is_floating_point_v<To>;
35 static_assert(is_integral || is_floating_point);
36
37 using FromLimits = std::numeric_limits<From>;
38 using ToLimits = std::numeric_limits<To>;
39
40 constexpr bool check_positive_overflow =
41 is_integral ? ToLimits::digits < FromLimits::digits : ToLimits::max() < FromLimits::max();
42 constexpr bool check_negative_overflow =
43 is_integral
44 ?
45 // signed -> signed: possible loss if narrowing
46 // signed -> unsigned: loss if negative
47 FromLimits::is_signed && (!ToLimits::is_signed || ToLimits::digits < FromLimits::digits)
48 : ToLimits::lowest() > FromLimits::lowest();
49
50 std::string_view overflow_type{};
51
52 if constexpr (check_positive_overflow) {
53 if (input > static_cast<From>(ToLimits::max())) {
54 if (!FromLimits::has_infinity || !std::isinf(input)) {
55 overflow_type = "positive";
56 }
57 }
58 }
59
60 if constexpr (check_negative_overflow) {
61 if (input < static_cast<From>(ToLimits::lowest())) {
62 if (!FromLimits::has_infinity || !std::isinf(input)) {
63 overflow_type = "negative";
64 }
65 }
66 }
67
68 if (!overflow_type.empty()) {
69 throw Exception(fmt::format(
70 "Failed to convert {} {} into {} due to {} integer overflow",
71 compiler::GetTypeName<From>(),
72 static_cast<impl::PrintableValue<From>>(input),
73 compiler::GetTypeName<To>(),
74 overflow_type
75 ));
76 }
77
78 return static_cast<To>(input);
79}
80
81} // namespace utils
82
83USERVER_NAMESPACE_END