1// SPDX-FileCopyrightText: 2006-2024 Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2024 Knut Reinert & MPI für molekulare Genetik
3// SPDX-License-Identifier: BSD-3-Clause
6 * \brief The [<charconv> header](https://en.cppreference.com/w/cpp/header/charconv) from C++17's standard library.
7 * \author Svenja Mehringer <svenja.mehringer AT fu-berlin.de>
10// File might be included from multiple libraries.
11#ifndef SEQAN_STD_CHARCONV_SHIM
12#define SEQAN_STD_CHARCONV_SHIM
15#include <utility> // __cpp_lib_to_chars may be defined here as currently documented.
16#include <version> // From C++20 onwards, all feature macros should be defined here.
18/*!\defgroup std_charconv charconv
20 * \brief The [<charconv> header](https://en.cppreference.com/w/cpp/header/charconv) from C++17's standard library.
23 * The following table describes what implementation of std::to_chars and std::from_chars will be used
25 * | stdlib version | __cpp_lib_to_chars | chars_format | to_chars_result | from_chars_result | to_chars (int) | from_chars (int) | to_chars (float) | from_chars (float) |
26 * | -------------- | ------------------------------------------------ | -------------- | --------------- | ----------------- | -------------- | ---------------- | -------------------- | -------------------- |
27 * | gcc 11 | undefined (or 201611) and `<charconv>` header | stdlib | stdlib | stdlib | stdlib | stdlib | stdlib | stdlib |
29 * Note: gcc 11 implements float too, but does not define __cpp_lib_to_chars
32// =========================================================================
33// If float implementation is missing, add our own shim-implementation
34// =========================================================================
36#if __cpp_lib_to_chars < 201611
41namespace seqan3::contrib::charconv_float
43using ::std::chars_format;
44using ::std::from_chars_result;
45using ::std::to_chars_result;
47/*!\brief std::to_chars implementation for floating point via a std::stringstream for default base = 10.
48 * \ingroup std_charconv
50template <std::floating_point value_type>
51inline to_chars_result to_chars_floating_point(char * first, char * last, value_type value) noexcept
53 assert(first != nullptr);
54 assert(last != nullptr);
56 std::ostringstream ss;
60 if (last - first < static_cast<std::ptrdiff_t>(str.size()))
61 return {last, std::errc::value_too_large};
63 std::copy(str.begin(), str.end(), first);
65 return {first + str.size(), std::errc{}};
68/*!\brief Delegates to functions strto[d/f/ld] for floating point value extraction.
69 * \ingroup std_charconv
71template <std::floating_point value_type>
72inline from_chars_result from_chars_floating_point(char const * first,
75 chars_format fmt = chars_format::general) noexcept
78 // std::from_chars is documented to be locale independent. The accepted patterns
79 // are identical to the one used by strtod in the defailt ("C") locale.
81 // The functions strto[d/f/ld] used here are locale dependent but
82 // setting the locale manually by std::setlocale is not thread safe.
83 // So for the time being this workaround is locale dependent.
84 if (*first == '+') // + is permitted in function strto[d/f/ld] but not in from_chars
85 return {last, std::errc::invalid_argument};
88 constexpr ptrdiff_t buffer_size = 100;
89 char buffer[buffer_size];
91 if (fmt != chars_format::general)
93 bool exponent_is_present{false};
94 for (auto it = first; it != last; ++it)
96 if (*it == 'e' || *it == 'E')
98 exponent_is_present = true;
103 if (fmt == chars_format::scientific && !exponent_is_present)
104 return {last, std::errc::invalid_argument};
106 if (fmt == chars_format::fixed && exponent_is_present)
107 return {last, std::errc::invalid_argument};
110 // In contrast to std::from_chars, std::strto[f/d/ld] does not treat the second
111 // parameter (str_end) as "end of the sequence to parse" but merely as an out
112 // parameter to indicate where the parsing ended. Therefore, if [last] does
113 // not point to the end of a null-terminated string, a buffer is needed to
114 // represent the truncated sequence and ensure correct from_chars functionality.
117 if ((*last != '\0') || fmt == chars_format::hex)
119 // If hex format is explicitly expected, the 0x prefix is not allowed in the
120 // the original sequence according to the std::from_chars cppreference
122 // In order to use strto[f/d/ld], the prefix must be prepended to achieve
123 // correct parsing. This will also automatically lead to an error if the
124 // original sequence did contain a 0x prefix and thus reflect the correct
125 // requirements of std::from_chars.
127 if (fmt == chars_format::hex)
134 std::copy(first, last, &buffer[offset]);
135 buffer[std::min<ptrdiff_t>(buffer_size - offset, last - first)] = '\0';
141 start = const_cast<char *>(first);
146 if constexpr (std::same_as<std::remove_reference_t<value_type>, float>)
148 tmp = strtof(start, &end);
150 if constexpr (std::same_as<std::remove_reference_t<value_type>, double>)
152 tmp = strtod(start, &end);
154 if constexpr (std::same_as<std::remove_reference_t<value_type>, long double>)
156 tmp = strtold(start, &end);
159 last = first + (end - start);
163 return {last, std::errc::result_out_of_range};
165 else if (tmp == 0 && end == start)
167 return {last, std::errc::invalid_argument};
175} // namespace seqan3::contrib::charconv_float
177namespace seqan3::contrib::charconv_float
179// -----------------------------------------------------------------------------
180// to_chars for floating point types
181// -----------------------------------------------------------------------------
183/*!\brief std::to_chars overload for floating point via a std::stringstream for default base = 10.
184 * \ingroup std_charconv
186template <std::floating_point floating_point_type>
187inline to_chars_result to_chars(char * first, char * last, floating_point_type value) noexcept
189 return to_chars_floating_point(first, last, value);
192// -----------------------------------------------------------------------------
193// from_chars for floating point types
194// -----------------------------------------------------------------------------
196/*!\brief Parse a char sequence into an floating point value.
197 * \ingroup std_charconv
198 * \tparam floating_point_type The type to parse the string into; Must model std::floating_point.
199 * \param[in] first The start of the string to parse.
200 * \param[in] last The end of the string to parse.
201 * \param[in, out] value The value to store the parsed result in.
202 * \param[in] fmt The std::chars_format that alters the behaviour of parsing.
203 * \returns A std::from_char_result. See detail section return value for more information.
207 * Analyzes the character sequence [first,last) for a pattern described below.
208 * If no characters match the pattern or if the value obtained by parsing the
209 * matched characters is not representable in the type of value, value is
210 * unmodified, otherwise the characters matching the pattern are interpreted as
211 * a text representation of an arithmetic value, which is stored in value.
213 * Floating-point parsers: Expects the pattern identical to the one used by
214 * std::strtod in the default ("C") locale, except that:
216 * - the plus sign is not recognized outside of the exponent (only the minus
217 * sign is permitted at the beginning)
218 * - if fmt has std::chars_format::scientific set but not std::chars_format::fixed,
219 * the exponent part is required (otherwise it is optional)
220 * - if fmt has std::chars_format::fixed set but not std::chars_format::scientific,
221 * the optional exponent is not permitted
222 * - if fmt is std::chars_format::hex, the prefix "0x" or "0X" is not permitted
223 * (the string "0x123" parses as the value "0" with unparsed remainder "x123").
225 * \attention This implementation is a workaround until the function is supported
226 * by the compiler. It falls back to use the functions strto[d/f/ld]
227 * before checking the above limitations
230 * This function is workaround until the function is supported
231 * by the compiler. It falls back to use the functions strto[d/f/ld] so the
232 * return value is NOT as documented here https://en.cppreference.com/w/cpp/utility/from_chars
235 * On success, std::from_chars_result::ec is value-initialized. On error,
236 * std::from_chars_result::ec is either an
237 * std::errc::invalid_argument if an illegal character or format has been
238 * encountered, or std::errc::out_of_range if parsing the value would cause an
239 * overflow. The std::from_chars_result::ptr value is always set to last.
241 * ### The locale issue
242 * std::from_chars is documented to be locale independent. The accepted patterns
243 * are identical to the one used by strtod in the defailt ("C") locale.
245 * The functions strto[d/f/ld] used here are locale dependent but
246 * setting the locale manually by std::setlocale is not thread safe.
247 * So for the time being this workaround is locale dependent.
249 * \sa https://en.cppreference.com/w/cpp/utility/from_chars
251template <std::floating_point floating_point_type>
252inline from_chars_result from_chars(char const * first,
254 floating_point_type & value,
255 chars_format fmt = chars_format::general) noexcept
257 return from_chars_floating_point(first, last, value, fmt);
259} // namespace seqan3::contrib::charconv_float
263// gcc-11 also defines float versions, but they don't clash with ours, because they use explicit overloads for each
264// float type. That means the stdlib has a higher priority in overload resolution then our shim implementation.
265using ::seqan3::contrib::charconv_float::from_chars; // import our shim-float version
266using ::seqan3::contrib::charconv_float::to_chars; // import our shim-float version
269#endif // __cpp_lib_to_chars < 201611
271#endif // SEQAN_STD_CHARCONV_SHIM