1
0
Files
YYCCommonplace/src/yycc/patch/libcxx/charconv.hpp
yyc12345 71eb0741f6 fix: try fix clang libcxx charconv issue.
- add compile checker in cmake to detect charconv support status.
- and expose macro for yycc to enable different part of charconv repectively to prevent duplicated defines.
2026-01-23 14:46:09 +08:00

447 lines
15 KiB
C++

#pragma once
#include "../../macro/stl_detector.hpp"
//#if defined(YYCC_STL_CLANGSTL)
/**
* @private
* @file This is the polyfill for LLVM libcxx charconv header
* which including \c std::from_chars and \c std::to_chars utilities.
* These 2 functions are only \b fully implemented in the latest Clang (20+)
* and partially implemented in the latest Apple Clang (Xcode 16).
* This should be removed once both Clang official and Apple Clang libcxx \b fully provide them.
* This polyfill is generated by AI.
*/
#include <charconv>
#include <system_error>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cerrno>
#include <cinttypes>
#include <type_traits>
#include <concepts>
#include <stdexcept>
#include <optional>
namespace std {
#if !defined(YYCC_CHARCONV_HAS_CHARS_FORMAT)
enum class chars_format : unsigned int {
scientific = 1,
fixed = 2,
hex = 4,
general = fixed | scientific // This should be 6 (fixed|scientific)
};
#endif
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_RESULT)
struct from_chars_result {
const char* ptr;
std::errc ec;
friend bool operator==(const from_chars_result&, const from_chars_result&) = default;
constexpr explicit operator bool() const noexcept { return ec == std::errc{}; }
};
#endif
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_RESULT)
struct to_chars_result {
char* ptr;
std::errc ec;
friend bool operator==(const to_chars_result&, const to_chars_result&) = default;
constexpr explicit operator bool() const noexcept { return ec == std::errc{}; }
};
#endif
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_INT)
/// @private
enum class __integral_type {
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
};
/// @private
enum class __integral_base_type {
base8,
base10,
base16,
};
/// @private
template<typename T>
constexpr __integral_type __classify_int_type() {
if constexpr (std::is_same_v<T, std::uint8_t>) {
return __integral_type::u8;
} else if constexpr (std::is_same_v<T, std::uint16_t>) {
return __integral_type::u16;
} else if constexpr (std::is_same_v<T, std::uint32_t>) {
return __integral_type::u32;
} else if constexpr (std::is_same_v<T, std::uint64_t>) {
return __integral_type::u64;
} else if constexpr (std::is_same_v<T, std::int8_t>) {
return __integral_type::i8;
} else if constexpr (std::is_same_v<T, std::int16_t>) {
return __integral_type::i16;
} else if constexpr (std::is_same_v<T, std::int32_t>) {
return __integral_type::i32;
} else if constexpr (std::is_same_v<T, std::int64_t>) {
return __integral_type::i64;
} else {
static_cast(false, "Unsupported integral type");
}
}
/// @private
std::optional<__integral_base_type> __classify_int_base(int base) {
if (base == 8) {
return __integral_base_type::base8;
} else if (base == 10) {
return __integral_base_type::base10;
} else if (base == 16) {
return __integral_base_type::base16;
} else {
return std::nullopt;
}
}
/// @private
/// @brief Helper to get printf format specifier based on type and base
template<__integral_type EIntTy>
const char* __get_int_format(__integral_base_type base) {
if (base == __integral_base_type::base8) {
if constexpr (EIntTy == __integral_type::u8) {
return "%" PRIo8;
} else if constexpr (EIntTy == __integral_type::u16) {
return "%" PRIo16;
} else if constexpr (EIntTy == __integral_type::u32) {
return "%" PRIo32;
} else if constexpr (EIntTy == __integral_type::u64) {
return "%" PRIo64;
} else {
static_assert(false, "Unsupported integral type and base");
}
} else if (base == __integral_base_type::base10) {
if constexpr (EIntTy == __integral_type::u8) {
return "%" PRIu8;
} else if constexpr (EIntTy == __integral_type::u16) {
return "%" PRIu16;
} else if constexpr (EIntTy == __integral_type::u32) {
return "%" PRIu32;
} else if constexpr (EIntTy == __integral_type::u64) {
return "%" PRIu64;
} else if constexpr (EIntTy == __integral_type::i8) {
return "%" PRId8;
} else if constexpr (EIntTy == __integral_type::i16) {
return "%" PRId16;
} else if constexpr (EIntTy == __integral_type::i32) {
return "%" PRId32;
} else if constexpr (EIntTy == __integral_type::i64) {
return "%" PRId64;
}
} else if (base == __integral_base_type::base16) {
if constexpr (EIntTy == __integral_type::u8) {
return "%" PRIx8;
} else if constexpr (EIntTy == __integral_type::u16) {
return "%" PRIx16;
} else if constexpr (EIntTy == __integral_type::u32) {
return "%" PRIx32;
} else if constexpr (EIntTy == __integral_type::u64) {
return "%" PRIx64;
} else {
static_assert(false, "Unsupported integral type and base");
}
}
}
// Integer to_chars
template<typename T>
requires std::integral<T> && (!std::same_as<T, bool>)
to_chars_result to_chars(char* first, char* last, T value, int base = 10) {
if (first >= last) {
return {first, std::errc::value_too_large};
}
constexpr auto integral_type = __classify_int_type<T>();
const auto opt_integral_base_type = __classify_int_base(base);
if (!opt_integral_base_type.has_value()) {
return {first, std::errc::invalid_argument};
}
const auto integral_base_type = std::move(opt_integral_base_type.value());
// Use snprintf with appropriate format
const auto max_buffer_size = static_cast<size_t>(last - first);
const char* format_string = __get_int_format<integral_type>(integral_base_type);
int written = std::snprintf(first, max_buffer_size, format_string, value);
if (written < 0 || static_cast<size_t>(written) >= max_buffer_size) {
return {last, std::errc::value_too_large};
}
return {first + written, std::errc{}};
}
#endif
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_FLOAT)
/// @private
enum class __float_type {
f32,
f64,
};
/// @private
template<typename T>
constexpr __float_type __classify_float_type() {
if constexpr (std::is_same_v<T, float>) {
return __float_type::f32;
} else if constexpr (std::is_same_v<T, double>) {
return __float_type::f64;
} else {
static_assert(false, "Unsupported floating point type");
}
}
/// @private
enum class __float_fmt_type { general, scientific, fixed, hex };
/// @private
std::optional<__float_fmt_type> __classify_float_fmt(chars_format fmt) {
if (fmt == chars_format::general) {
return __float_fmt_type::general;
} else if (fmt == chars_format::scientific) {
return __float_fmt_type::scientific;
} else if (fmt == chars_format::fixed) {
return __float_fmt_type::fixed;
} else if (fmt == chars_format::hex) {
return __float_fmt_type::hex;
} else {
return std::nullopt;
}
}
/// @private
template<__float_type TFpTy>
std::optional<const char*> __get_float_format(chars_format fmt) {
// Precision is passed by extra argument via ".*" format.
if (fmt == chars_format::general) {
if constexpr (TFpTy == __float_type::f32) {
return "%.*g";
} else if constexpr (TFpTy == __float_type::f64) {
return "%.*lg";
}
} else if (fmt == chars_format::scientific) {
if constexpr (TFpTy == __float_type::f32) {
return "%.*e";
} else if constexpr (TFpTy == __float_type::f64) {
return "%.*le";
}
} else if (fmt == chars_format::fixed) {
if constexpr (TFpTy == __float_type::f32) {
return "%.*f";
} else if constexpr (TFpTy == __float_type::f64) {
return "%.*lf";
}
} else if (fmt == chars_format::hex) {
if constexpr (TFpTy == __float_type::f32) {
return "%.*a";
} else if constexpr (TFpTy == __float_type::f64) {
return "%.*la";
}
} else {
return std::nullopt;
}
}
// Float to_chars
template<typename T>
requires std::floating_point<T>
to_chars_result to_chars(char* first, char* last, T value, chars_format fmt, int precision = 0) {
if (first >= last) {
return {first, std::errc::value_too_large};
}
constexpr auto float_type = __classify_float_type<T>();
const auto opt_float_fmt_type = __classify_float_fmt(fmt);
if (!opt_float_fmt_type.has_value()) {
return {first, std::errc::invalid_argument};
}
const auto float_fmt_type = std::move(opt_float_fmt_type.value());
const auto max_buffer_size = static_cast<size_t>(last - first);
const char* format_string = __get_float_format<float_type>(float_fmt_type);
int written = std::snprintf(first, max_buffer_size, format_string, precision, value);
if (written < 0 || static_cast<size_t>(written) >= max_buffer_size) {
return {last, std::errc::value_too_large};
}
return {first + written, std::errc{}};
}
#endif
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_INT)
/// @private
enum class __strtoi_cluster { tol, toll, toul, toull };
/// @private
template<typename T>
constexpr __strtoi_cluster __classify_strtoi_cluster() {
if constexpr (std::is_signed_v<T>) {
if constexpr (sizeof(T) <= sizeof(long)) {
return __strtoi_cluster::tol;
} else if constexpr (sizeof(T) <= sizeof(long long)) {
return __strtoi_cluster::toll;
} else {
static_assert(false, "Unsupported signed integral type");
}
} else {
if constexpr (sizeof(T) <= sizeof(unsigned long)) {
return __strtoi_cluster::toul;
} else if constexpr (sizeof(T) <= sizeof(unsigned long long)) {
return __strtoi_cluster::toull;
} else {
static_assert(false, "Unsupported unsigned integral type");
}
}
}
/// @private
template<__strtoi_cluster TFc>
auto __execute_strtoi(const char* str, char** str_end, int base) {
if constexpr (TFc == __strtoi_cluster::tol) {
return std::strtol(str, str_end, base);
} else if constexpr (TFc == __strtoi_cluster::toll) {
return std::strtoll(str, str_end, base);
} else if constexpr (TFc == __strtoi_cluster::toul) {
return std::strtoul(str, str_end, base);
} else if constexpr (TFc == __strtoi_cluster::toull) {
return std::strtoull(str, str_end, base);
}
}
// Integer from_chars
template<typename T>
requires std::integral<T> && (!std::same_as<T, bool>)
from_chars_result from_chars(const char* first, const char* last, T& value, int base = 10) {
if (first >= last) {
return {first, std::errc::invalid_argument};
}
// strtoi function cluster strongly order that given string must be NULL-terminated.
// So we must do a heavy copy in there because first-last pair is not NULL-terminated guaranteed.
std::string buffer(first, static_cast<size_t>(last - first));
constexpr auto strtoi_cluster = __classify_strtoi_cluster<T>();
errno = 0;
char* end_ptr = const_cast<char*>(first);
auto result = __execute_strtoi<strtoi_cluster>(buffer.data(), &end_ptr, base);
if (errno == ERANGE) {
return {end_ptr, std::errc::result_out_of_range};
}
using strtoi_cluster_rvtype = decltype(result);
// Check if result fits in T
if (result < static_cast<strtoi_cluster_rvtype>(std::numeric_limits<T>::min())
|| result > static_cast<strtoi_cluster_rvtype>(std::numeric_limits<T>::max())) {
return {end_ptr, std::errc::result_out_of_range};
}
if (end_ptr == buffer.data()) {
return {first, std::errc::invalid_argument};
}
// Ensure we don't go past 'last'
if ((end_ptr - buffer.data()) > (last - first)) {
return {const_cast<char*>(last), std::errc::invalid_argument};
}
value = static_cast<T>(result);
return {first + (end_ptr - buffer.data()), std::errc{}};
}
#endif
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT)
/// @private
enum class __strtof_cluster { tof, tod };
/// @private
template<typename T>
constexpr __strtof_cluster __classify_strtof_cluster() {
if constexpr (std::is_same_v<T, float>) {
return __strtof_cluster::tof;
} else if constexpr (std::is_same_v<T, double>) {
return __strtof_cluster::tod;
} else {
static_assert(false, "Unsupported floating point type");
}
}
/// @private
template<__strtof_cluster TFc>
auto __execute_strtof(const char* str, char** str_end) {
if constexpr (TFc == __strtof_cluster::tof) {
return std::strtof(str, str_end);
} else if constexpr (TFc == __strtof_cluster::tod) {
return std::strtod(str, str_end);
}
}
// Float from_chars
template<typename T>
requires std::floating_point<T>
from_chars_result from_chars(const char* first, const char* last, T& value, chars_format fmt = chars_format::general) {
// We ignore "fmt" by design.
if (first >= last) {
return {first, std::errc::invalid_argument};
}
// strtof function cluster strongly order that given string must be NULL-terminated.
// So we must do a heavy copy in there because first-last pair is not NULL-terminated guaranteed.
std::string buffer(first, static_cast<size_t>(last - first));
constexpr auto strtof_cluster = __classify_strtof_cluster<T>();
errno = 0;
char* end_ptr = const_cast<char*>(first);
auto result = __execute_strtof<strtof_cluster>(buffer.data(), &end_ptr);
if (errno == ERANGE) {
return {end_ptr, std::errc::result_out_of_range};
}
if (end_ptr == buffer.data()) {
return {first, std::errc::invalid_argument};
}
// Ensure we don't go past 'last'
if ((end_ptr - buffer.data()) > (last - first)) {
return {const_cast<char*>(last), std::errc::invalid_argument};
}
value = result;
return {first + (end_ptr - buffer.data()), std::errc{}};
}
#endif
} // namespace std
//#endif