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 <string>
13#include <string_view>
14#include <type_traits>
15#include <typeinfo>
16
17#include <userver/utils/expected.hpp>
18#include <userver/utils/zstring_view.hpp>
19
20USERVER_NAMESPACE_BEGIN
21
22namespace utils {
23
24/// @brief Conversion error code.
26 /// @brief String contains leading whitespace characters.
28
29 /// @brief String contains invalid (non-digit) characters at the end.
30 kTrailingJunk = 2,
31
32 /// @brief String does not contain a number.
33 kNoNumber = 3,
34
35 /// @brief Conversion result is out of the valid range of the specified type.
36 kOverflow = 4
37};
38
39/// @brief Converts @a code to string representation.
40constexpr inline std::string_view ToString(FromStringErrorCode code) noexcept {
41 switch (code) {
43 return "leading spaces are not allowed";
45 return "extra junk at the end of the string is not allowed";
47 return "no number found";
49 return "overflow";
50 default:
51 return "unknown";
52 }
53}
54
55/// @brief Function `utils::FromString` exception type.
56class FromStringException : public std::runtime_error {
57public:
58 /// @brief Creates exception for @a code .
59 FromStringException(FromStringErrorCode code, const std::string& what);
60
61 /// @brief Returns conversion error code.
62 FromStringErrorCode GetCode() const noexcept { return code_; }
63
64private:
66};
67
68namespace impl {
69
70template <typename T, typename = void>
71struct IsFromCharsConvertible : std::false_type {};
72
73template <typename T>
74struct IsFromCharsConvertible<
75 T,
76 std::void_t<decltype(std::from_chars(std::declval<const char*>(), std::declval<const char*>(), std::declval<T&>())
77 )>> : std::true_type {};
78
79// libstdc++ before 13.1 parse long double incorrectly
80#if defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 13
81template <>
82struct IsFromCharsConvertible<long double> : std::false_type {};
83#endif
84
85template <class T>
86inline constexpr bool kIsFromCharsConvertible = IsFromCharsConvertible<T>::value;
87
88[[noreturn]] void ThrowFromStringException(
90 std::string_view input,
91 const std::type_info& result_type
92);
93
94template <typename T>
95std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, expected<T, FromStringErrorCode>>
96FromString(utils::zstring_view str) noexcept {
97 static_assert(!std::is_const_v<T> && !std::is_volatile_v<T>);
98 static_assert(!std::is_reference_v<T>);
99
100 if (str.empty()) {
101 return unexpected{FromStringErrorCode::kNoNumber};
102 }
103 if (std::isspace(str.front())) {
104 return unexpected{FromStringErrorCode::kLeadingSpaces};
105 }
106 if (str.size() > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
107 return unexpected{FromStringErrorCode::kTrailingJunk};
108 }
109
110 errno = 0;
111 char* end = nullptr;
112
113 const auto result = [&] {
114 if constexpr (std::is_same_v<T, float>) {
115 return std::strtof(str.c_str(), &end);
116 } else if constexpr (std::is_same_v<T, double>) {
117 return std::strtod(str.c_str(), &end);
118 } else if constexpr (std::is_same_v<T, long double>) {
119 return std::strtold(str.c_str(), &end);
120 }
121 }();
122
123 if (errno == ERANGE && !(result < 1 && result > 0.0)) {
124 return unexpected{FromStringErrorCode::kOverflow};
125 }
126
127 if (end == str.c_str()) {
128 return unexpected{FromStringErrorCode::kNoNumber};
129 }
130
131 if (end != str.data() + str.size()) {
132 return unexpected{FromStringErrorCode::kTrailingJunk};
133 }
134
135 return result;
136}
137
138template <typename T>
139std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, expected<T, FromStringErrorCode>>
140FromString(const std::string& str) noexcept {
141 return impl::FromString<T>(utils::zstring_view{str});
142}
143
144template <typename T>
145std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, expected<T, FromStringErrorCode>>
146FromString(const char* str) noexcept {
147 return impl::FromString<T>(utils::zstring_view{str});
148}
149
150template <typename T>
151std::enable_if_t<std::is_floating_point_v<T> && !kIsFromCharsConvertible<T>, expected<T, FromStringErrorCode>>
152FromString(std::string_view str) noexcept {
153 static constexpr std::size_t kSmallBufferSize = 32;
154
155 if (str.size() >= kSmallBufferSize) {
156 const std::string buffer{str};
157 return impl::FromString<T>(utils::zstring_view{buffer});
158 }
159
160 char buffer[kSmallBufferSize];
161 std::copy(str.data(), str.data() + str.size(), buffer);
162 buffer[str.size()] = '\0';
163
164 return impl::FromString<T>(utils::zstring_view::UnsafeMake(buffer, str.size()));
165}
166
167template <typename T>
168std::enable_if_t<kIsFromCharsConvertible<T>, expected<T, FromStringErrorCode>> FromString(std::string_view str
169) noexcept {
170 static_assert(!std::is_const_v<T> && !std::is_volatile_v<T>);
171 static_assert(!std::is_reference_v<T>);
172
173 if (str.empty()) {
174 return unexpected{FromStringErrorCode::kNoNumber};
175 }
176 if (std::isspace(str[0])) {
177 return unexpected{FromStringErrorCode::kLeadingSpaces};
178 }
179
180 std::size_t offset = 0;
181
182 // to allow leading plus
183 if (str.size() > 1 && str[0] == '+' && str[1] == '-') {
184 return unexpected{FromStringErrorCode::kNoNumber};
185 }
186 if (str[0] == '+') {
187 offset = 1;
188 }
189
190 // to process '-0' correctly
191 if (std::is_unsigned_v<T> && str[0] == '-') {
192 offset = 1;
193 }
194
195 T result{};
196 const auto [end, error_code] = std::from_chars(str.data() + offset, str.data() + str.size(), result);
197
198 if (error_code == std::errc::result_out_of_range) {
199 return unexpected{FromStringErrorCode::kOverflow};
200 }
201 if (error_code == std::errc::invalid_argument) {
202 return unexpected{FromStringErrorCode::kNoNumber};
203 }
204
205 if (std::is_unsigned_v<T> && str[0] == '-' && result != 0) {
206 return unexpected{FromStringErrorCode::kOverflow};
207 }
208
209 if (end != str.data() + str.size()) {
210 return unexpected{FromStringErrorCode::kTrailingJunk};
211 }
212
213 return result;
214}
215
216} // namespace impl
217
218/// @brief Extract the number contained in the string. No space characters or
219/// other extra characters allowed. Supported types:
220///
221/// - Integer types. Leading plus or minus is allowed. The number is always
222/// base-10.
223/// - Floating-point types. The accepted number format is identical to
224/// `std::strtod`.
225///
226/// @tparam T The type of the number to be parsed
227/// @param str The string that contains the number
228/// @return The extracted number
229/// @throw FromStringException if the string does not contain an integer or
230/// floating-point number in the specified format, or the string contains extra
231/// junk, or the number does not fit into the provided type
232template <
233 typename T,
234 typename StringType,
235 typename = std::enable_if_t<std::is_convertible_v<StringType, std::string_view>>>
236T FromString(const StringType& str) {
237 const auto result = impl::FromString<T>(str);
238
239 if (result) {
240 return result.value();
241 } else {
242 impl::ThrowFromStringException(result.error(), str, typeid(T));
243 }
244}
245
246/// @brief Extract the number contained in the string. No space characters or
247/// other extra characters allowed. Supported types:
248///
249/// - Integer types. Leading plus or minus is allowed. The number is always
250/// base-10.
251/// - Floating-point types. The accepted number format is identical to
252/// `std::strtod`.
253///
254/// @tparam T The type of the number to be parsed
255/// @param str The string that contains the number
256/// @return `utils::expected` with the conversion result or error code
257template <
258 typename T,
259 typename StringType,
260 typename = std::enable_if_t<std::is_convertible_v<StringType, std::string_view>>>
261expected<T, FromStringErrorCode> FromStringNoThrow(const StringType& str) noexcept {
262 return impl::FromString<T>(str);
263}
264
265std::int64_t FromHexString(std::string_view str);
266
267} // namespace utils
268
269USERVER_NAMESPACE_END