Three issues in the extended-locale number parsing were breaking CI:
- On Windows, _create_locale() was called with LC_ALL_MASK, a POSIX-only
constant that doesn't exist in the Windows CRT; it needs LC_ALL there.
- `::isspace_l`/`::_isspace_l` were called with a leading `::`, but on
glibc, mingw-w64 and some other C libraries these are macros that expand
to a parenthesized expression, so `::` followed by the expansion is a
syntax error ("expected unqualified-id"). Dropping the `::` fixes this
on all affected toolchains (gcc/clang on Linux, nvcc, mingw).
- The Windows implementation assumed the full `_<funcname>_l` family is
always available, but some MinGW toolchains only provide a subset
(e.g. missing `_strtoll_l`/`_strtoull_l`/`_strtof_l`/`_strtold_l`).
The extended-locale fast path is now only used for genuine MSVC
(including clang-cl, which mimics the MSVC ABI/CRT); other Windows
toolchains fall back to the locale-dependent standard functions.
Also fixes a clang-tidy hicpp-named-parameter/readability-named-parameter
warning-as-error on two unnamed `init_val_t` test parameters.
Verified locally with gcc/clang on Linux, clang++ in C++20 mode, and
mingw-w64 (both g++ and clang targeting x86_64-w64-mingw32).
On Windows, `_strtof_l` et al. are always available. POSIX on the other
hand only covers `newlocale` and `freelocale` but the extended locale
functions `strtof_l` provided by glibc or `<xlocale.h>` on BSDs cannot
be assumed to be universally available. For this reason, the trait
`has_extended_locale_support` uses SFINAE to detect whether `strtof_l`
is defined and falls back to the locale-dependent standard functions if
it isn't.
Signed-off-by: Jonas Greitemann <jgreitemann@gmail.com>
`std::from_chars` is locale-independent and thus avoids the TOCTOU
issue #5198 when it's available.
This commit introduces a `from_chars` utility in the `detail` namespace
which merely wraps `std::from_chars` if it is available and emulates its
API and behavior using the `strto*` family of functions when it is not.
As of this commit, the `strto*`-based fallback is still locale-dependent
and only matches `std::from_chars` when the "C" locale is used.
Availability of `std::from_chars` for floating point numbers is varied.
For instance, as of libc++ 22, `std::from_chars` for `long double` isn't
supported and, consequently, the feature-test macro `__cpp_lib_to_chars`
is not set, while the overloads for `float` and `double` are available.
To avoid complicating matters further, the feature-test macro is taken
as the authoritative indicator for (full) `std::from_chars` support and
the fallback is used otherwise.
If `std::from_chars` is available, our wrapper additionally tweaks its
output in the case of floating-point out-of-range results. The behavior
in this case is poorly (under-)specified and existing implementations do
not agree. The wrapper's tweaks are intended to ensure consistent
behavior across all toolchains in this edge case. In practice, this
should only take effect when parsing out-of-range floats on libstdc++.
The unit tests for the `from_chars` helper are intentionally compiled in
both C++11 and C++17 modes. This ensures that the test expectations
align with the actual behavior we're trying to model. The tests are not
aiming to cover all of floating-point parsing comprehensively, as
`std::from_chars` is specified to behave mostly the same as `strto*`
and instead focuses on the edge cases where the two differ:
- leading whitespace is not tolerated
- `+` sign is not allowed (outside of the exponent)
- out-parameter is left unchanged in case of `invalid_argument` and
integral `result_out_of_range` errors
Signed-off-by: Jonas Greitemann <jgreitemann@gmail.com>