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