wesnoth/src/utils/charconv.hpp
gfgtdf 5801b5bcdb
use to/from_chars in attribute_value and lexical_cast (#8790)
This makes attribute_value and lexical_cast use the "new" to/from_chars api.

Its main advantages are:
- It's guaranteed to be locale independent, hopefully fixing all cases of #3945 and similar
- It fixes some cases config serialization, in particular the test
```
	cfg["x"] = "9.87654321";
	BOOST_CHECK_EQUAL(cfg["x"], 9.87654321);
```
- Previously the lexical_cast implementation used exception
  handling for invalid formats (catching std::invalid_argument)
  which made noise during debugging (and is also slower if it
  is not optimized out).
- It's faster

So far afaik the only compiler which has a complete and proper to/from_chars implementation is msvc, gccs implementation of from_chars sometimes uses strtod under the hood and clang simply hasn't implemented from_chars for floating point numbers yet at all (actually the upcomig clang 20 will have it). Luckily for us, there is now also boost::charconv that can be used. So this raises to minimum build requirement to have at least one of:

- msvc 2019 update 5
- gcc 11
- clang 14 (i have added a fallback implementation of from_chars for this case, that doesn't support all of its features, and is probably certainly not as fast as the boost version, but supports the features that we use from it)
- boost 1.85
 
Since in particular the gcc implementation isn't that good (at least it on gcc11), boost charconv is the preferred implementation that is used if available.

This also removes a strange overload for
pointers to integers in lexical_cast while changing lexical_cast to use the new api.
2024-12-08 22:28:10 +01:00

138 lines
3.8 KiB
C++

/*
Copyright (C) 2024
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include <boost/version.hpp>
#include <array>
#include <assert.h>
#include <cctype>
#include <string_view>
#include <string>
// The gcc implemenetation of from_chars is in some versions just a temporaty solution that calls
// strtod that's why we prefer the boost version if available.
#if BOOST_VERSION >= 108500 && __has_include(<boost/charconv.hpp>)
#define USE_BOOST_CHARCONV
#elif defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE >= 11
#define USE_STD_CHARCONV
#elif defined(_MSC_VER) && _MSC_VER >= 1924
#define USE_STD_CHARCONV
#elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 14000
#define USE_FALLBACK_CHARCONV
#else
#error No charconv implementation found.
#endif
#ifdef USE_BOOST_CHARCONV
#include <boost/charconv.hpp>
namespace charconv_impl = boost::charconv;
#else
#include <charconv>
namespace charconv_impl = std;
#endif
namespace utils::charconv
{
using chars_format = charconv_impl::chars_format;
using from_chars_result = charconv_impl::from_chars_result;
using to_chars_result = charconv_impl::to_chars_result;
template<typename... T>
to_chars_result to_chars(char* first, char* last, T&&... value )
{
return charconv_impl::to_chars(first, last, value...);
}
template<typename T>
std::enable_if_t<std::is_integral_v<T>, from_chars_result> from_chars(const char* first, const char* last, T& value, int base = 10 )
{
return charconv_impl::from_chars(first, last, value, base);
}
#ifndef USE_FALLBACK_CHARCONV
template<typename T>
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 )
{
return charconv_impl::from_chars(first, last, value, fmt);
}
#else
template<typename T>
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 );
#endif
// the maximum size of a string that to_chars produces for type T, with the default chars_format
template<class T>
constexpr size_t buffer_size = 50;
}
namespace utils
{
// from_chars doesnt support leading whitespaces, call this before from_chars if you want to accept leading whitespaces.
inline void trim_for_from_chars(std::string_view& v)
{
while(!v.empty() && std::isspace(v.front())) {
v.remove_prefix(1);
}
if(v.size() >= 2 && v[0] == '+' && v[1] != '-' ) {
v.remove_prefix(1);
}
}
// converts a number to a char buffer without allocations.
template<typename TNum>
struct charconv_buffer
{
std::array<char, utils::charconv::buffer_size<TNum>> buffer;
size_t size;
charconv_buffer()
: size(0)
{
}
charconv_buffer(TNum num)
: size(0)
{
set_value(num);
}
void set_value(TNum num)
{
auto [ptr, ec] = utils::charconv::to_chars(buffer.data(), buffer.data() + buffer.size(), num);
if(ec != std::errc()) {
// Shouldnt happen by definition of utils::charconv::buffer_size<TNum>
assert(!"Error in charconv_buffer, buffer not large enough");
size = 0;
} else {
size = ptr - buffer.data();
}
//TODO: should we make this null-terminated?
}
std::string_view get_view() const
{
return std::string_view(buffer.data(), size);
}
std::string to_string() const
{
return std::string(buffer.data(), size);
}
};
}