The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
charconv.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2024 - 2025
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13*/
14
15#pragma once
16
17#include <boost/version.hpp>
18
19#include <array>
20#include <assert.h>
21#include <cctype>
22#include <charconv>
24#include <string_view>
25#include <string>
26#include <stdexcept>
27// The gcc implemenetation of from_chars is in some versions just a temporaty solution that calls
28// strtod that's why we prefer the boost version if available.
29#if BOOST_VERSION >= 108500 && __has_include(<boost/charconv.hpp>)
30 #define USE_BOOST_CHARCONV
31#elif defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE >= 11
32 #define USE_STD_CHARCONV
33#elif defined(_MSC_VER) && _MSC_VER >= 1924
34 #define USE_STD_CHARCONV
35#elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 14000
36 #define USE_FALLBACK_CHARCONV
37#else
38 #error No charconv implementation found.
39#endif
40
41#ifdef USE_BOOST_CHARCONV
42#include <boost/charconv.hpp>
43namespace charconv_impl = boost::charconv;
44#else
45namespace charconv_impl = std;
46#endif
47
48
50{
51 using chars_format = charconv_impl::chars_format;
52 using from_chars_result = charconv_impl::from_chars_result;
53 using to_chars_result = charconv_impl::to_chars_result;
54
55 template<typename... T>
56 to_chars_result to_chars(char* first, char* last, T&&... value )
57 {
58 return charconv_impl::to_chars(first, last, value...);
59 }
60
61 template<typename T>
62 std::enable_if_t<std::is_integral_v<T>, std::from_chars_result> from_chars(const char* first, const char* last, T& value, int base = 10 )
63 {
64 return std::from_chars(first, last, value, base);
65 }
66
67#ifndef USE_FALLBACK_CHARCONV
68 template<typename T>
69 std::enable_if_t<std::is_floating_point_v<T>, from_chars_result> from_chars(const char* first, const char* last, T& value, chars_format fmt = chars_format::general )
70 {
71 return charconv_impl::from_chars(first, last, value, fmt);
72 }
73#else
74 template<typename T>
75 std::enable_if_t<std::is_same_v<T, double> || std::is_same_v<T, float> || std::is_same_v<T, long double>, from_chars_result> from_chars(const char* first, const char* last, T& value, chars_format fmt = chars_format::general );
76#endif
77
78 // the maximum size of a string that to_chars produces for type T, with the default chars_format
79 template<class T>
80 constexpr std::size_t buffer_size = 50;
81}
82
83namespace utils
84{
85 // from_chars doesnt support leading whitespaces, call this before from_chars if you want to accept leading whitespaces.
86 inline void trim_for_from_chars(std::string_view& v)
87 {
88 while(!v.empty() && std::isspace(v.front())) {
89 v.remove_prefix(1);
90 }
91 if(v.size() >= 2 && v[0] == '+' && v[1] != '-' ) {
92 v.remove_prefix(1);
93 }
94 }
95
96
97 // converts a number to a char buffer without allocations.
98 template<typename TNum>
100 {
101 std::array<char, utils::charconv::buffer_size<TNum>> buffer;
102 std::size_t size;
103
105 : size(0)
106 {
107 }
108
110 : size(0)
111 {
112 set_value(num);
113 }
114
115 void set_value(TNum num)
116 {
117 auto [ptr, ec] = utils::charconv::to_chars(buffer.data(), buffer.data() + buffer.size(), num);
118 if(ec != std::errc()) {
119 // Shouldnt happen by definition of utils::charconv::buffer_size<TNum>
120 assert(!"Error in charconv_buffer, buffer not large enough");
121 size = 0;
122 } else {
123 size = ptr - buffer.data();
124 }
125 //TODO: should we make this null-terminated?
126 }
127
128 std::string_view get_view() const
129 {
130 return std::string_view(buffer.data(), size);
131 }
132
133 std::string to_string() const
134 {
135 return std::string(buffer.data(), size);
136 }
137 };
138
139 /// Same interface as std::stod and meant as a drop in replacement, except:
140 /// - It takes a std::string_view
141 /// - It is locale independent
142 inline double stod(std::string_view str) {
144 double res;
145 auto [ptr, ec] = utils::charconv::from_chars(str.data(), str.data() + str.size(), res);
146 if(ec == std::errc::invalid_argument) {
147 throw std::invalid_argument("Failed to convert string to double: input contains invalid characters or is not a valid number.");
148 } else if(ec == std::errc::result_out_of_range) {
149 throw std::out_of_range("Failed to convert string to double: input value is out of the representable range for a double.");
150 }
151 return res;
152 }
153
154 /// Same interface as std::stoi and meant as a drop in replacement, except:
155 /// - It takes a std::string_view
156 inline int stoi(std::string_view str) {
158 int res;
159 auto [ptr, ec] = utils::charconv::from_chars(str.data(), str.data() + str.size(), res);
160 if(ec == std::errc::invalid_argument) {
161 throw std::invalid_argument("Failed to convert string to int: input contains invalid characters or is not a valid number.");
162 } else if(ec == std::errc::result_out_of_range) {
163 throw std::out_of_range("Failed to convert string to int: input value is out of the representable range for an int.");
164 }
165 return res;
166 }
167
168#ifdef __cpp_concepts
169 template<std::integral T>
170 inline auto from_chars(std::string_view str, int base = 10) -> utils::optional<T>
171#else
172 template<typename T>
173 inline auto from_chars(std::string_view str, int base = 10) -> std::enable_if_t<std::is_integral_v<T>, utils::optional<T>>
174#endif
175 {
177 T result{};
178 const auto [_, ec] = utils::charconv::from_chars(str.data(), str.data() + str.size(), result, base);
179 return ec == std::errc{} ? utils::make_optional(result) : utils::nullopt;
180 }
181
182#ifdef __cpp_concepts
183 template<std::floating_point T>
184 inline auto from_chars(std::string_view str,
185 utils::charconv::chars_format fmt = utils::charconv::chars_format::general) -> utils::optional<T>
186#else
187 template<typename T>
188 inline auto from_chars(std::string_view str,
189 utils::charconv::chars_format fmt = utils::charconv::chars_format::general) -> std::enable_if_t<std::is_floating_point_v<T>, utils::optional<T>>
190#endif
191 {
193 T result{};
194 const auto [_, ec] = utils::charconv::from_chars(str.data(), str.data() + str.size(), result, fmt);
195 return ec == std::errc{} ? utils::make_optional(result) : utils::nullopt;
196 }
197}
static std::string _(const char *str)
Definition gettext.hpp:100
STL namespace.
std::enable_if_t< std::is_integral_v< T >, std::from_chars_result > from_chars(const char *first, const char *last, T &value, int base=10)
Definition charconv.hpp:62
charconv_impl::from_chars_result from_chars_result
Definition charconv.hpp:52
charconv_impl::chars_format chars_format
Definition charconv.hpp:51
to_chars_result to_chars(char *first, char *last, T &&... value)
Definition charconv.hpp:56
charconv_impl::to_chars_result to_chars_result
Definition charconv.hpp:53
constexpr std::size_t buffer_size
Definition charconv.hpp:80
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition charconv.hpp:156
void trim_for_from_chars(std::string_view &v)
Definition charconv.hpp:86
double stod(std::string_view str)
Same interface as std::stod and meant as a drop in replacement, except:
Definition charconv.hpp:142
auto from_chars(std::string_view str, int base=10) -> std::enable_if_t< std::is_integral_v< T >, utils::optional< T > >
Definition charconv.hpp:173
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
void set_value(TNum num)
Definition charconv.hpp:115
std::string_view get_view() const
Definition charconv.hpp:128
std::string to_string() const
Definition charconv.hpp:133
std::array< char, utils::charconv::buffer_size< TNum > > buffer
Definition charconv.hpp:101