From d63bbdf9f7c74870dd0794387327999f43bf2063 Mon Sep 17 00:00:00 2001 From: Thomas Willson Date: Tue, 7 Jul 2026 22:48:19 +0000 Subject: [PATCH] Add snprintf fallback for float/double formatting on toolchains without floating std::to_chars Integer std::to_chars is universally available, but the floating-point overloads are not implemented everywhere: IAR's libc++ (bxarm) ships std::to_chars(float/double) as deleted functions, so StringBuilder.cpp fails to compile there even when no float/double is ever formatted. Guard the four floating-point formatters (append/prepend for float and double) behind NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS, which defaults to 0 under IAR and 1 elsewhere, and can be overridden by the consumer. When disabled, format via std::snprintf("%.*g", max_digits10, ...) into a stack buffer and reuse the existing append/prepend(string_view) path. Integer formatting is untouched. Co-Authored-By: Claude Opus 4.8 (1M context) --- include/nfx/detail/string/StringBuilder.inl | 45 +++++++++++++++++++++ include/nfx/string/StringBuilder.h | 16 ++++++++ 2 files changed, 61 insertions(+) diff --git a/include/nfx/detail/string/StringBuilder.inl b/include/nfx/detail/string/StringBuilder.inl index 74db4dc..cc6b828 100644 --- a/include/nfx/detail/string/StringBuilder.inl +++ b/include/nfx/detail/string/StringBuilder.inl @@ -33,6 +33,11 @@ #include #include +#if !NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS +# include +# include +#endif + namespace nfx::string { //===================================================================== @@ -298,11 +303,21 @@ namespace nfx::string { ensureCapacity( m_size + FLOAT_MAX_CHARS ); } +#if NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value ); if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY { m_size = ptr - m_buffer; } +#else + char scratch[FLOAT_MAX_CHARS + 1]; + const int written = std::snprintf( scratch, sizeof( scratch ), "%.*g", + std::numeric_limits::max_digits10, static_cast( value ) ); + if( written > 0 ) + { + return append( std::string_view{ scratch, static_cast( written ) } ); + } +#endif return *this; } @@ -312,11 +327,21 @@ namespace nfx::string { ensureCapacity( m_size + DOUBLE_MAX_CHARS ); } +#if NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value ); if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY { m_size = ptr - m_buffer; } +#else + char scratch[DOUBLE_MAX_CHARS + 1]; + const int written = std::snprintf( scratch, sizeof( scratch ), "%.*g", + std::numeric_limits::max_digits10, value ); + if( written > 0 ) + { + return append( std::string_view{ scratch, static_cast( written ) } ); + } +#endif return *this; } @@ -491,23 +516,43 @@ namespace nfx::string inline StringBuilder& StringBuilder::prepend( float value ) { +#if NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS char buffer[FLOAT_MAX_CHARS]; auto [ptr, ec] = std::to_chars( buffer, buffer + FLOAT_MAX_CHARS, value ); if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY { return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } ); } +#else + char buffer[FLOAT_MAX_CHARS + 1]; + const int written = std::snprintf( buffer, sizeof( buffer ), "%.*g", + std::numeric_limits::max_digits10, static_cast( value ) ); + if( written > 0 ) + { + return prepend( std::string_view{ buffer, static_cast( written ) } ); + } +#endif return *this; } inline StringBuilder& StringBuilder::prepend( double value ) { +#if NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS char buffer[DOUBLE_MAX_CHARS]; auto [ptr, ec] = std::to_chars( buffer, buffer + DOUBLE_MAX_CHARS, value ); if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY { return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } ); } +#else + char buffer[DOUBLE_MAX_CHARS + 1]; + const int written = std::snprintf( buffer, sizeof( buffer ), "%.*g", + std::numeric_limits::max_digits10, value ); + if( written > 0 ) + { + return prepend( std::string_view{ buffer, static_cast( written ) } ); + } +#endif return *this; } diff --git a/include/nfx/string/StringBuilder.h b/include/nfx/string/StringBuilder.h index 416d002..2169f6a 100644 --- a/include/nfx/string/StringBuilder.h +++ b/include/nfx/string/StringBuilder.h @@ -132,6 +132,22 @@ # define NFX_STRINGBUILDER_UNLIKELY #endif +/** + * @def NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS + * @brief Whether std::to_chars(float/double) is usable on this toolchain. + * @details Integer std::to_chars is universally available, but the + * floating-point overloads are not: IAR's libc++ ships them as deleted + * functions. When unavailable the float/double formatters fall back to + * std::snprintf. Define this to 0/1 before including to override. + */ +#if !defined( NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS ) +# if defined( __IAR_SYSTEMS_ICC__ ) +# define NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS 0 +# else +# define NFX_STRINGBUILDER_HAS_FLOAT_TO_CHARS 1 +# endif +#endif + namespace nfx::string { //=====================================================================