userver: userver/utils/from_string.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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#include <userver/utils/zstring_view.hpp>
21
22USERVER_NAMESPACE_BEGIN
23
24namespace utils {
25
26namespace impl {
27
28[[noreturn]] void
29ThrowFromStringException(std::string_view message, std::string_view input, 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, typeid(T));
44 }
45
46 errno = 0;
47 char* end = nullptr;
48
49 const auto result = [&] {
50 if constexpr (std::is_same_v<T, float>) {
51 return std::strtof(str, &end);
52 } else if constexpr (std::is_same_v<T, double>) {
53 return std::strtod(str, &end);
54 } else if constexpr (std::is_same_v<T, long double>) {
55 return std::strtold(str, &end);
56 }
57 }();
58
59 if (errno == ERANGE && !(result < 1 && result > 0.0)) {
60 impl::ThrowFromStringException("overflow", str, typeid(T));
61 }
62
63 if (end == str) {
64 impl::ThrowFromStringException("no number found", str, typeid(T));
65 }
66
67 if (*end != '\0') {
68 if (std::isspace(*end)) {
69 impl::ThrowFromStringException("trailing spaces are not allowed", str, typeid(T));
70 } else {
71 impl::ThrowFromStringException("extra junk at the end of the string is not allowed", str, typeid(T));
72 }
73 }
74
75 return result;
76}
77
78template <typename T>
79std::enable_if_t<std::is_floating_point_v<T>, T> FromString(const std::string& str) {
80 return impl::FromString<T>(str.c_str());
81}
82
83template <typename T>
84std::enable_if_t<std::is_floating_point_v<T>, T> FromString(utils::zstring_view str) {
85 return impl::FromString<T>(str.c_str());
86}
87
88template <typename T>
89std::enable_if_t<std::is_floating_point_v<T>, T> FromString(std::string_view str) {
90 static constexpr std::size_t kSmallBufferSize = 32;
91
92 if (str.size() >= kSmallBufferSize) {
93 return impl::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 impl::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, typeid(T));
113 }
114
115 std::size_t offset = 0;
116
117 // to allow leading plus
118 if (str.size() > 1 && str[0] == '+' && str[1] == '-') {
119 impl::ThrowFromStringException("no number found", str, typeid(T));
120 }
121 if (str[0] == '+') offset = 1;
122
123 // to process '-0' correctly
124 if (std::is_unsigned_v<T> && str[0] == '-') offset = 1;
125
126 T result{};
127 const auto [end, error_code] = std::from_chars(str.data() + offset, str.data() + str.size(), result);
128
129 if (error_code == std::errc::result_out_of_range) {
130 impl::ThrowFromStringException("overflow", str, typeid(T));
131 }
132 if (error_code == std::errc::invalid_argument) {
133 impl::ThrowFromStringException("no number found", str, typeid(T));
134 }
135
136 if (std::is_unsigned_v<T> && str[0] == '-' && result != 0) {
137 impl::ThrowFromStringException("overflow", str, typeid(T));
138 }
139
140 if (end != str.data() + str.size()) {
141 if (std::isspace(*end)) {
142 impl::ThrowFromStringException("trailing spaces are not allowed", str, typeid(T));
143 } else {
144 impl::ThrowFromStringException("extra junk at the end of the string is not allowed", str, typeid(T));
145 }
146 }
147
148 return result;
149}
150
151} // namespace impl
152
153/// @brief Extract the number contained in the string. No space characters or
154/// other extra characters allowed. Supported types:
155///
156/// - Integer types. Leading plus or minus is allowed. The number is always
157/// base-10.
158/// - Floating-point types. The accepted number format is identical to
159/// `std::strtod`.
160///
161/// @tparam T The type of the number to be parsed
162/// @param str The string that contains the number
163/// @return The extracted number
164/// @throw std::runtime_error if the string does not contain an integer or
165/// floating-point number in the specified format, or the string contains extra
166/// junk, or the number does not fit into the provided type
167template <
168 typename T,
169 typename StringType,
170 typename = std::enable_if_t<std::is_convertible_v<StringType, std::string_view>>>
171T FromString(const StringType& str) {
172 return impl::FromString<T>(str);
173}
174
175std::int64_t FromHexString(std::string_view str);
176
177} // namespace utils
178
179USERVER_NAMESPACE_END