userver: userver/utils/from_string.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
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
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