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 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