ADTF
Loading...
Searching...
No Matches
string_conversion.h
Go to the documentation of this file.
1
7#pragma once
8#include <string>
9#include <string_view>
10#include <sstream>
11#include <locale>
12#include <array>
13#include <limits>
14
15#ifdef _WIN32
16 #define STRING_CONV_HAVE_CHAR_CONV
17 #include <charconv>
18#else
19 #if defined __has_include
20 #if __has_include(<charconv>)
21 #include <charconv>
22 #ifdef __cpp_lib_to_chars
23 #define STRING_CONV_HAVE_CHAR_CONV
24 #endif
25 #endif
26 #endif
27#endif
28
29namespace adtf::base::string_conversion
30{
31
32enum class float_format
33{
34 general = 0,
35 scientific = 1,
36 fixed = 2,
37 hex = 3
38};
39
40namespace detail
41{
42
43#ifdef STRING_CONV_HAVE_CHAR_CONV
44
45template<typename T>
46inline std::string float_to_string(T value, float_format format)
47{
48 std::chars_format target_format = std::chars_format::fixed;
49 switch (format)
50 {
51 case float_format::general: target_format = std::chars_format::general; break;
52 case float_format::fixed: target_format = std::chars_format::fixed; break;
53 case float_format::scientific: target_format = std::chars_format::scientific; break;
54 case float_format::hex: target_format = std::chars_format::hex; break;
55 }
56
57 std::array<char, std::numeric_limits<T>::max_exponent10 + 2> buffer{};
58 const auto conversion_result = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value, target_format);
59 if (conversion_result.ec != std::errc())
60 {
61 throw std::runtime_error("cannot convert number to string: " +
62 std::make_error_code(conversion_result.ec).message());
63 }
64 else
65 {
66 return std::string(buffer.data(), conversion_result.ptr);
67 }
68}
69
70template<typename T>
71inline T string_to_float(std::string_view value)
72{
73 T result = 0;
74 const auto conversion_result = std::from_chars(value.data(), value.data() + value.size(), result);
75 if (conversion_result.ec != std::errc())
76 {
77 throw std::runtime_error("cannot convert string '" + std::string(value) +
78 "' to number: " + std::make_error_code(conversion_result.ec).message());
79 }
80 return result;
81}
82
83#else
84
85template<typename T>
86inline std::string float_to_string(T value, float_format format)
87{
88 std::ostringstream oStream;
89 oStream.imbue(std::locale::classic());
90
91 switch (format)
92 {
93 case float_format::general: break;
94 case float_format::fixed: oStream << std::fixed; break;
95 case float_format::scientific: oStream << std::scientific; break;
96 case float_format::hex: oStream << std::hexfloat; break;
97 }
98
99 oStream << value;
100
101 return oStream.str();
102}
103
104template<typename T>
105inline T string_to_float(std::string_view value)
106{
107 T result = {};
108 std::istringstream oStream{std::string(value)};
109 oStream.imbue(std::locale::classic());
110 oStream >> result;
111 if (oStream.fail())
112 {
113 throw std::invalid_argument("not a number");
114 }
115 return result;
116}
117
118#endif
119
120inline std::pair<int, size_t> get_base(std::string_view value)
121{
122 const size_t nSignLength = (!value.empty() && (value.front() == '-' || value.front() == '+')) ? 1 : 0;
123 const std::string_view strDigits = value.substr(nSignLength);
124
125 if (strDigits.size() > 1 && strDigits[0] == '0')
126 {
127 if (strDigits[1] == 'x' || strDigits[1] == 'X')
128 {
129 // hexadecimal detected
130 return {16, nSignLength + 2};
131 }
132 if (strDigits[1] == 'b' || strDigits[1] == 'B')
133 {
134 // binary detected
135 return {2, nSignLength + 2};
136 }
137 if (strDigits[1] >= '0' && strDigits[1] <= '7')
138 {
139 // octal detected
140 return {8, nSignLength + 1};
141 }
142 }
143
144 return {10, nSignLength};
145}
146
147} // namespace detail
148
149template<typename T>
150inline std::string to_string(T value, float_format /* format */ = float_format::fixed)
151{
152 return std::to_string(value);
153}
154
155template<>
156inline std::string to_string(float value, float_format format)
157{
158 return detail::float_to_string(value, format);
159}
160
161template<>
162inline std::string to_string(double value, float_format format)
163{
164 return detail::float_to_string(value, format);
165}
166
187template<typename T>
188inline T to_number(std::string_view value, int base = 0)
189{
190 T result = 0;
191
192 bool negative = false;
193 if (base == 0)
194 {
195 const auto base_helper = detail::get_base(value);
196 base = base_helper.first;
197
198 // std::from_chars() cannot parse a "0x"/"0b" prefix (sto* does not support 0b), so
199 // get_base() reports the prefix length (including a leading sign) to strip. Because a sign
200 // that is stripped together with the prefix can no longer be parsed by std::from_chars(),
201 // it is remembered here and re-applied afterwards. Octal and decimal values are reported
202 // with a zero-length prefix, so their sign is parsed directly by std::from_chars().
203 if (base_helper.second > 0)
204 {
205 negative = !value.empty() && value.front() == '-';
206 value = value.substr(base_helper.second);
207 }
208 }
209
210#ifdef STRING_CONV_HAVE_CHAR_CONV
211 const auto fnConvert = [&](std::string_view value, auto& result, int base)
212 {
213 const auto conversion_result = std::from_chars(value.data(), value.data() + value.size(), result, base);
214 if (conversion_result.ec != std::errc())
215 {
216 const auto strMessage = "cannot convert string '" + std::string(value) +
217 "' to number: " + std::make_error_code(conversion_result.ec).message();
218
219 if (conversion_result.ec == std::errc::result_out_of_range)
220 {
221 throw std::out_of_range(strMessage);
222 }
223
224 throw std::runtime_error(strMessage);
225 }
226 };
227#else
228 const auto fnConvert = [&](std::string_view value, auto& result, int base)
229 {
230 using U = std::decay_t<decltype(result)>;
231 try
232 {
233 if constexpr (std::is_signed_v<U>)
234 {
235 const auto helper = std::stoll(std::string(value), 0, base);
236 if (helper > (std::numeric_limits<U>::max)() || helper < std::numeric_limits<U>::lowest())
237 {
238 throw std::out_of_range("out of range");
239 }
240 result = static_cast<U>(helper);
241 }
242 else
243 {
244 const auto helper = std::stoull(std::string(value), 0, base);
245 if (helper > (std::numeric_limits<U>::max)())
246 {
247 throw std::out_of_range("out of range");
248 }
249 result = static_cast<U>(helper);
250 }
251 }
252 catch (const std::out_of_range& oError)
253 {
254 const auto strMessage = "cannot convert string '" + std::string(value) + "' to number: " + oError.what();
255 throw std::out_of_range(strMessage);
256 }
257 };
258#endif
259
260 if (negative)
261 {
262 if constexpr (std::is_signed_v<T>)
263 {
264 // The sign was stripped together with the base prefix, so the bare
265 // magnitude is parsed into the unsigned counterpart first. This also
266 // allows the most negative value (e.g. "-0x80" -> -128), whose
267 // magnitude does not fit into the positive range of the signed type.
268 using unsigned_t = std::make_unsigned_t<T>;
269 unsigned_t magnitude = 0;
270 fnConvert(value, magnitude, base);
271
272 const unsigned_t max_magnitude = static_cast<unsigned_t>((std::numeric_limits<T>::max)()) + 1;
273 if (magnitude > max_magnitude)
274 {
275 throw std::out_of_range("cannot convert string '" + std::string(value) + "' to number: " +
276 std::make_error_code(std::errc::result_out_of_range).message());
277 }
278
279 result = static_cast<T>(0 - magnitude);
280 }
281 else
282 {
283 throw std::runtime_error("cannot convert string '" + std::string(value) +
284 "' to number: negative value is not representable as an unsigned type");
285 }
286 }
287 else
288 {
289 fnConvert(value, result, base);
290 }
291
292 return result;
293}
294
295template<>
296inline float to_number(std::string_view value, int /*base = 10*/)
297{
298 return detail::string_to_float<float>(value);
299}
300
301template<>
302inline double to_number(std::string_view value, int /*base = 10*/)
303{
304 return detail::string_to_float<double>(value);
305}
306
307} // namespace adtf::base::string_conversion
Namespace for the ADTF Base SDK.
Definition adtf_base_type_traits.h:13
T to_number(std::string_view value, int base=0)
Definition string_conversion.h:188