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