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