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