userver: userver/utils/from_string.hpp Source File
Loading...
Searching...
No Matches
from_string.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/from_string.hpp
4/// @brief @copybrief utils::FromString
5/// @ingroup userver_universal
6
7#include <cctype>
8#include <cerrno>
9#include <charconv>
10#include <cstdint>
11#include <cstdlib>
12#include <limits>
13#include <string>
14#include <string_view>
15#include <type_traits>
16#include <typeindex>
17#include <typeinfo>
18
19#include <userver/utils/zstring_view.hpp>
20
21USERVER_NAMESPACE_BEGIN
22
23namespace utils {
24
25namespace impl {
26
27template <typename T, typename = void>
28struct IsFromCharsConvertible : std::false_type {};
29
30template <typename T>
31struct IsFromCharsConvertible<
32 T,
33 std::void_t<decltype(std::from_chars(std::declval<const char*>(), std::declval<const char*>(), std::declval<T&>())
34 )>> : std::true_type {};
35
36template <class T>
37inline constexpr bool kIsFromCharsConvertible = IsFromCharsConvertible<T>::value;
38
39[[noreturn]] void
40ThrowFromStringException(std::string_view message, std::string_view input, std::type_index result_type);
41
42template <typename T>
43std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, T> FromString(utils::zstring_view str) {
44 static_assert(!std::is_const_v<T> && !std::is_volatile_v<T>);
45 static_assert(!std::is_reference_v<T>);
46
47 if (str.empty()) {
48 impl::ThrowFromStringException("empty string", str, typeid(T));
49 }
50 if (std::isspace(str.front())) {
51 impl::ThrowFromStringException("leading spaces are not allowed", str, typeid(T));
52 }
53 if (str.size() > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
54 impl::ThrowFromStringException("extra junk at the end of the string is not allowed", str, typeid(T));
55 }
56
57 errno = 0;
58 char* end = nullptr;
59
60 const auto result = [&] {
61 if constexpr (std::is_same_v<T, float>) {
62 return std::strtof(str.c_str(), &end);
63 } else if constexpr (std::is_same_v<T, double>) {
64 return std::strtod(str.c_str(), &end);
65 } else if constexpr (std::is_same_v<T, long double>) {
66 return std::strtold(str.c_str(), &end);
67 }
68 }();
69
70 if (errno == ERANGE && !(result < 1 && result > 0.0)) {
71 impl::ThrowFromStringException("overflow", str, typeid(T));
72 }
73
74 if (end == str.c_str()) {
75 impl::ThrowFromStringException("no number found", str, typeid(T));
76 }
77
78 if (end != str.data() + str.size()) {
79 if (std::isspace(*end)) {
80 impl::ThrowFromStringException("trailing spaces are not allowed", str, typeid(T));
81 } else {
82 impl::ThrowFromStringException("extra junk at the end of the string is not allowed", str, typeid(T));
83 }
84 }
85
86 return result;
87}
88
89template <typename T>
90std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, T> FromString(const std::string& str) {
91 return impl::FromString<T>(utils::zstring_view{str});
92}
93
94template <typename T>
95std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, T> FromString(const char* str) {
96 return impl::FromString<T>(utils::zstring_view{str});
97}
98
99template <typename T>
100std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, T> FromString(std::string_view str) {
101 static constexpr std::size_t kSmallBufferSize = 32;
102
103 if (str.size() >= kSmallBufferSize) {
104 const std::string buffer{str};
105 return impl::FromString<T>(utils::zstring_view{buffer});
106 }
107
108 char buffer[kSmallBufferSize];
109 std::copy(str.data(), str.data() + str.size(), buffer);
110 buffer[str.size()] = '\0';
111
112 return impl::FromString<T>(utils::zstring_view::UnsafeMake(buffer, str.size()));
113}
114
115template <typename T>
116std::enable_if_t<kIsFromCharsConvertible<T>, T> FromString(std::string_view str) {
117 static_assert(!std::is_const_v<T> && !std::is_volatile_v<T>);
118 static_assert(!std::is_reference_v<T>);
119
120 if (str.empty()) {
121 impl::ThrowFromStringException("empty string", str, typeid(T));
122 }
123 if (std::isspace(str[0])) {
124 impl::ThrowFromStringException("leading spaces are not allowed", str, typeid(T));
125 }
126
127 std::size_t offset = 0;
128
129 // to allow leading plus
130 if (str.size() > 1 && str[0] == '+' && str[1] == '-') {
131 impl::ThrowFromStringException("no number found", str, typeid(T));
132 }
133 if (str[0] == '+') offset = 1;
134
135 // to process '-0' correctly
136 if (std::is_unsigned_v<T> && str[0] == '-') offset = 1;
137
138 T result{};
139 const auto [end, error_code] = std::from_chars(str.data() + offset, str.data() + str.size(), result);
140
141 if (error_code == std::errc::result_out_of_range) {
142 impl::ThrowFromStringException("overflow", str, typeid(T));
143 }
144 if (error_code == std::errc::invalid_argument) {
145 impl::ThrowFromStringException("no number found", str, typeid(T));
146 }
147
148 if (std::is_unsigned_v<T> && str[0] == '-' && result != 0) {
149 impl::ThrowFromStringException("overflow", str, typeid(T));
150 }
151
152 if (end != str.data() + str.size()) {
153 if (std::isspace(*end)) {
154 impl::ThrowFromStringException("trailing spaces are not allowed", str, typeid(T));
155 } else {
156 impl::ThrowFromStringException("extra junk at the end of the string is not allowed", str, typeid(T));
157 }
158 }
159
160 return result;
161}
162
163} // namespace impl
164
165/// @brief Extract the number contained in the string. No space characters or
166/// other extra characters allowed. Supported types:
167///
168/// - Integer types. Leading plus or minus is allowed. The number is always
169/// base-10.
170/// - Floating-point types. The accepted number format is identical to
171/// `std::strtod`.
172///
173/// @tparam T The type of the number to be parsed
174/// @param str The string that contains the number
175/// @return The extracted number
176/// @throw std::runtime_error if the string does not contain an integer or
177/// floating-point number in the specified format, or the string contains extra
178/// junk, or the number does not fit into the provided type
179template <
180 typename T,
181 typename StringType,
182 typename = std::enable_if_t<std::is_convertible_v<StringType, std::string_view>>>
183T FromString(const StringType& str) {
184 return impl::FromString<T>(str);
185}
186
187std::int64_t FromHexString(std::string_view str);
188
189} // namespace utils
190
191USERVER_NAMESPACE_END