Use C++17 <charconv> for wxString to/from number conversion

The "new" std::{to,from}_chars() functions are much faster and more
robust than the previously used code, so use them if they are available.
This commit is contained in:
Vadim Zeitlin 2023-04-05 16:00:15 +02:00
parent 9b1d031a1b
commit 65c048568f

View file

@ -1560,7 +1560,119 @@ bool wxString::ToDouble(double *pVal) const
);
}
#if wxUSE_XLOCALE
// There are several possibilities for implementing the conversion functions
// always using "C" locale:
//
// 1. Preferred one: use C++17 <charconv>, this is the fastest way to do it.
// 2. Use <xlocale.h> if it's available.
// 3. Use standard locale-dependent C functions and adjust them for the
// current locale (slowest and the least robust).
// Check if C++17 <charconv> is available.
#if wxCHECK_CXX_STD(201703L)
#include <charconv>
#endif
// Now check if the functions we need are present in it (normally they ought
// to if the compiler claims to support C++17, but it doesn't hurt to check).
#ifdef __cpp_lib_to_chars
bool wxString::ToCLong(long *pVal, int base) const
{
wxCHECK_MSG( pVal, false, "null output pointer" );
const wxScopedCharBuffer& buf = utf8_str();
auto start = buf.data();
const auto end = start + buf.length();
// from_chars() doesn't recognize base==0 and doesn't recognize "0x" prefix
// even if base 16 is explicitly specified, so adjust the input to use the
// form it supports.
if ( buf.length() > 1 && *start == '0' )
{
++start;
if ( *start == 'x' || *start == 'X' )
{
++start;
if ( base == 0 )
base = 16;
else if ( base != 16 )
return false;
}
else
{
if ( base == 0 )
base = 8;
}
}
if ( base == 0 )
base = 10;
const auto res = std::from_chars(start, end, *pVal, base);
return res.ec == std::errc{} && res.ptr == end;
}
bool wxString::ToCULong(unsigned long *pVal, int base) const
{
// We intentionally don't use std::from_chars() here because this function
// is supposed to be compatible with strtoul() and so _succeed_ for "-1",
// for example, instead of returning an error as from_chars() (much more
// logically) does.
wxCHECK_MSG( pVal, false, "null output pointer" );
long l;
if ( !ToCLong(&l, base) )
return false;
*pVal = static_cast<unsigned long>(l);
return true;
}
bool wxString::ToCDouble(double *pVal) const
{
wxCHECK_MSG( pVal, false, "null output pointer" );
const wxScopedCharBuffer& buf = utf8_str();
const auto start = buf.data();
const auto end = start + buf.length();
const auto res = std::from_chars(start, end, *pVal);
return res.ec == std::errc{} && res.ptr == end;
}
wxString wxString::FromCDouble(double val, int precision)
{
wxCHECK_MSG( precision >= -1, wxString(), "Invalid negative precision" );
// 64 digits is more than enough for any double.
char buf[64];
const auto start = buf;
const auto end = buf + sizeof(buf);
std::to_chars_result res;
// Note that we must explicitly specify the precision to remain compatible
// with the behaviour of sprintf("%g"): by default, the result would be the
// shortest string avoiding precision loss, but "%g" is supposed to
// truncate, so use its default precision explicitly to achieve this here.
if ( precision == -1 )
res = std::to_chars(start, end, val, std::chars_format::general, 6);
else
res = std::to_chars(start, end, val, std::chars_format::fixed, precision);
if ( res.ec != std::errc{} )
return {};
*res.ptr = '\0';
return wxString::FromAscii(buf);
}
#elif wxUSE_XLOCALE
bool wxString::ToCLong(long *pVal, int base) const
{
@ -1688,6 +1800,8 @@ wxString wxString::FromDouble(double val, int precision)
return wxString::Format(format, val);
}
#ifndef __cpp_lib_to_chars
/* static */
wxString wxString::FromCDouble(double val, int precision)
{
@ -1721,6 +1835,8 @@ wxString wxString::FromCDouble(double val, int precision)
return s;
}
#endif // !__cpp_lib_to_chars
// ---------------------------------------------------------------------------
// formatted output
// ---------------------------------------------------------------------------