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 char* end = nullptr;
48 errno = 0;
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) {
61 impl::ThrowFromStringException("overflow", str, typeid(T));
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,
70 typeid(T));
71 } else {
72 impl::ThrowFromStringException(
73 "extra junk at the end of the string is not allowed", str, typeid(T));
74 }
75 }
76
77 return result;
78}
79
80template <typename T>
81std::enable_if_t<std::is_floating_point_v<T>, T> FromString(
82 const std::string& str) {
83 return FromString<T>(str.data());
84}
85
86template <typename T>
87std::enable_if_t<std::is_floating_point_v<T>, T> FromString(
88 std::string_view str) {
89 static constexpr std::size_t kSmallBufferSize = 32;
90
91 if (str.size() >= kSmallBufferSize) {
92 return FromString<T>(std::string{str});
93 }
94
95 char buffer[kSmallBufferSize];
96 std::copy(str.data(), str.data() + str.size(), buffer);
97 buffer[str.size()] = '\0';
98
99 return FromString<T>(buffer);
100}
101
102template <typename T>
103std::enable_if_t<meta::kIsInteger<T>, T> FromString(std::string_view str) {
104 static_assert(!std::is_const_v<T> && !std::is_volatile_v<T>);
105 static_assert(!std::is_reference_v<T>);
106
107 if (str.empty()) {
108 impl::ThrowFromStringException("empty string", str, typeid(T));
109 }
110 if (std::isspace(str[0])) {
111 impl::ThrowFromStringException("leading spaces are not allowed", str,
112 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] =
128 std::from_chars(str.data() + offset, str.data() + str.size(), result);
129
130 if (error_code == std::errc::result_out_of_range) {
131 impl::ThrowFromStringException("overflow", str, typeid(T));
132 }
133 if (error_code == std::errc::invalid_argument) {
134 impl::ThrowFromStringException("no number found", str, typeid(T));
135 }
136
137 if (std::is_unsigned_v<T> && str[0] == '-' && result != 0) {
138 impl::ThrowFromStringException("overflow", str, typeid(T));
139 }
140
141 if (end != str.data() + str.size()) {
142 if (std::isspace(*end)) {
143 impl::ThrowFromStringException("trailing spaces are not allowed", str,
144 typeid(T));
145 } else {
146 impl::ThrowFromStringException(
147 "extra junk at the end of the string is not allowed", str, typeid(T));
148 }
149 }
150
151 return result;
152}
153
154} // namespace impl
155
156/// @brief Extract the number contained in the string. No space characters or
157/// other extra characters allowed. Supported types:
158///
159/// - Integer types. Leading plus or minus is allowed. The number is always
160/// base-10.
161/// - Floating-point types. The accepted number format is identical to
162/// `std::strtod`.
163///
164/// @tparam T The type of the number to be parsed
165/// @param str The string that contains the number
166/// @return The extracted number
167/// @throw std::runtime_error if the string does not contain an integer or
168/// floating-point number in the specified format, or the string contains extra
169/// junk, or the number does not fit into the provided type
170template <typename T, typename StringType,
171 typename = std::enable_if_t<
173T FromString(const StringType& str) {
174 return impl::FromString<T>(str);
175}
176
177std::int64_t FromHexString(const std::string& str);
178
179} // namespace utils
180
181USERVER_NAMESPACE_END