diff --git a/.github/workflows/build-linux-clang.yml b/.github/workflows/build-linux-clang.yml
index f343495..09e7d93 100644
--- a/.github/workflows/build-linux-clang.yml
+++ b/.github/workflows/build-linux-clang.yml
@@ -23,7 +23,11 @@ permissions:
jobs:
build-linux-clang:
runs-on: ubuntu-latest
- name: Ubuntu Clang 18
+ name: Ubuntu Clang 18 (C++${{ matrix.std }})
+ strategy:
+ fail-fast: false
+ matrix:
+ std: [17, 20]
steps:
- name: Checkout repository
@@ -37,9 +41,9 @@ jobs:
path: |
build
~/.cache/ccache
- key: ${{ runner.os }}-clang18-ninja-${{ hashFiles('**/CMakeLists.txt', 'include/**') }}
+ key: ${{ runner.os }}-clang18-cxx${{ matrix.std }}-ninja-${{ hashFiles('**/CMakeLists.txt', 'include/**') }}
restore-keys: |
- ${{ runner.os }}-clang18-ninja-
+ ${{ runner.os }}-clang18-cxx${{ matrix.std }}-ninja-
- name: Set up build environment
run: |
@@ -65,6 +69,7 @@ jobs:
cmake -B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
+ -DNFX_STRINGBUILDER_CXX_STANDARD=${{ matrix.std }} \
-DNFX_STRINGBUILDER_BUILD_STATIC=ON \
-DNFX_STRINGBUILDER_BUILD_SHARED=ON \
-DNFX_STRINGBUILDER_BUILD_TESTS=ON \
@@ -83,7 +88,7 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
- name: test-results-linux-clang18
+ name: test-results-linux-clang18-cxx${{ matrix.std }}
path: |
build/Testing/Temporary/LastTest.log
build/Testing/Temporary/LastTestsFailed.log
diff --git a/.github/workflows/build-linux-gcc.yml b/.github/workflows/build-linux-gcc.yml
index bff649c..aead165 100644
--- a/.github/workflows/build-linux-gcc.yml
+++ b/.github/workflows/build-linux-gcc.yml
@@ -23,7 +23,11 @@ permissions:
jobs:
build-linux-gcc:
runs-on: ubuntu-latest
- name: Ubuntu GCC 14
+ name: Ubuntu GCC 14 (C++${{ matrix.std }})
+ strategy:
+ fail-fast: false
+ matrix:
+ std: [17, 20]
steps:
- name: Checkout repository
@@ -37,9 +41,9 @@ jobs:
path: |
build
~/.cache/ccache
- key: ${{ runner.os }}-gcc14-ninja-${{ hashFiles('**/CMakeLists.txt', 'include/**') }}
+ key: ${{ runner.os }}-gcc14-cxx${{ matrix.std }}-ninja-${{ hashFiles('**/CMakeLists.txt', 'include/**') }}
restore-keys: |
- ${{ runner.os }}-gcc14-ninja-
+ ${{ runner.os }}-gcc14-cxx${{ matrix.std }}-ninja-
- name: Set up build environment
run: |
@@ -65,6 +69,7 @@ jobs:
cmake -B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
+ -DNFX_STRINGBUILDER_CXX_STANDARD=${{ matrix.std }} \
-DNFX_STRINGBUILDER_BUILD_STATIC=ON \
-DNFX_STRINGBUILDER_BUILD_SHARED=ON \
-DNFX_STRINGBUILDER_BUILD_TESTS=ON \
@@ -83,7 +88,7 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
- name: test-results-linux-gcc14
+ name: test-results-linux-gcc14-cxx${{ matrix.std }}
path: |
build/Testing/Temporary/LastTest.log
build/Testing/Temporary/LastTestsFailed.log
diff --git a/.github/workflows/build-windows-mingw.yml b/.github/workflows/build-windows-mingw.yml
index ec48dcd..5be3255 100644
--- a/.github/workflows/build-windows-mingw.yml
+++ b/.github/workflows/build-windows-mingw.yml
@@ -23,7 +23,11 @@ permissions:
jobs:
build-windows-mingw:
runs-on: windows-2022
- name: Windows MinGW
+ name: Windows MinGW (C++${{ matrix.std }})
+ strategy:
+ fail-fast: false
+ matrix:
+ std: [17, 20]
steps:
- name: Checkout repository
@@ -37,9 +41,9 @@ jobs:
path: |
build
${{ runner.temp }}/ccache
- key: ${{ runner.os }}-mingw-ninja-${{ hashFiles('**/CMakeLists.txt', 'include/**') }}
+ key: ${{ runner.os }}-mingw-cxx${{ matrix.std }}-ninja-${{ hashFiles('**/CMakeLists.txt', 'include/**') }}
restore-keys: |
- ${{ runner.os }}-mingw-ninja-
+ ${{ runner.os }}-mingw-cxx${{ matrix.std }}-ninja-
- name: Set up MinGW
uses: msys2/setup-msys2@v2
@@ -72,6 +76,7 @@ jobs:
cmake -B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
+ -DNFX_STRINGBUILDER_CXX_STANDARD=${{ matrix.std }} \
-DNFX_STRINGBUILDER_BUILD_STATIC=ON \
-DNFX_STRINGBUILDER_BUILD_SHARED=ON \
-DNFX_STRINGBUILDER_BUILD_TESTS=ON \
@@ -94,7 +99,7 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
- name: test-results-windows-mingw
+ name: test-results-windows-mingw-cxx${{ matrix.std }}
path: |
build/Testing/Temporary/LastTest.log
build/Testing/Temporary/LastTestsFailed.log
diff --git a/.github/workflows/build-windows-msvc.yml b/.github/workflows/build-windows-msvc.yml
index 4b31f79..75427e5 100644
--- a/.github/workflows/build-windows-msvc.yml
+++ b/.github/workflows/build-windows-msvc.yml
@@ -23,7 +23,11 @@ permissions:
jobs:
build-windows-msvc:
runs-on: windows-2022
- name: Windows MSVC 2022
+ name: Windows MSVC 2022 (C++${{ matrix.std }})
+ strategy:
+ fail-fast: false
+ matrix:
+ std: [17, 20]
steps:
- name: Checkout repository
@@ -37,12 +41,12 @@ jobs:
path: |
build
C:\Users\runneradmin\AppData\Local\Microsoft\MSBuild
- key: ${{ runner.os }}-msvc2022-vs2022-${{ hashFiles('**/CMakeLists.txt', 'include/**') }}
+ key: ${{ runner.os }}-msvc2022-cxx${{ matrix.std }}-vs2022-${{ hashFiles('**/CMakeLists.txt', 'include/**') }}
restore-keys: |
- ${{ runner.os }}-msvc2022-vs2022-
+ ${{ runner.os }}-msvc2022-cxx${{ matrix.std }}-vs2022-
- name: Configure CMake
- run: cmake -B build -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DNFX_STRINGBUILDER_BUILD_STATIC=ON -DNFX_STRINGBUILDER_BUILD_SHARED=ON -DNFX_STRINGBUILDER_BUILD_TESTS=ON -DNFX_STRINGBUILDER_BUILD_SAMPLES=ON -DNFX_STRINGBUILDER_BUILD_BENCHMARKS=ON -DNFX_STRINGBUILDER_BUILD_DOCUMENTATION=OFF
+ run: cmake -B build -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DNFX_STRINGBUILDER_CXX_STANDARD=${{ matrix.std }} -DNFX_STRINGBUILDER_BUILD_STATIC=ON -DNFX_STRINGBUILDER_BUILD_SHARED=ON -DNFX_STRINGBUILDER_BUILD_TESTS=ON -DNFX_STRINGBUILDER_BUILD_SAMPLES=ON -DNFX_STRINGBUILDER_BUILD_BENCHMARKS=ON -DNFX_STRINGBUILDER_BUILD_DOCUMENTATION=OFF
- name: Build with MSVC
run: |
@@ -61,7 +65,7 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
- name: test-results-windows-msvc2022
+ name: test-results-windows-msvc2022-cxx${{ matrix.std }}
path: |
build/Testing/Temporary/LastTest.log
build/Testing/Temporary/LastTestsFailed.log
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 135ce3b..01910e0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,6 +38,12 @@ enable_testing()
option(NFX_STRINGBUILDER_BUILD_STATIC "Build static library" ON )
option(NFX_STRINGBUILDER_BUILD_SHARED "Build shared library" OFF)
+# --- Language standard ---
+# Minimum required standard is C++17. C++20 additionally enables the
+# std::format()-based formatting API and the std::formatter specialization.
+set(NFX_STRINGBUILDER_CXX_STANDARD "20" CACHE STRING "C++ standard used to build nfx-stringbuilder (17 or 20)")
+set_property(CACHE NFX_STRINGBUILDER_CXX_STANDARD PROPERTY STRINGS "17" "20")
+
# --- Build components ---
option(NFX_STRINGBUILDER_BUILD_TESTS "Build tests" OFF)
option(NFX_STRINGBUILDER_BUILD_SAMPLES "Build samples" OFF)
diff --git a/README.md b/README.md
index 063959b..f94814d 100644
--- a/README.md
+++ b/README.md
@@ -4,17 +4,17 @@
[](https://github.com/nfx-libs/nfx-stringbuilder/blob/main/LICENSE) [](https://github.com/nfx-libs/nfx-stringbuilder/releases) [](https://github.com/nfx-libs/nfx-stringbuilder/tags)
-  
+  
[](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/build-linux-gcc.yml) [](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/build-linux-clang.yml) [](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/build-windows-mingw.yml) [](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/build-windows-msvc.yml) [](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/codeql.yml)
-> A cross-platform C++20 high-performance string builder with Small Buffer Optimization and efficient memory management
+> A cross-platform C++17/C++20 high-performance string builder with Small Buffer Optimization and efficient memory management
## Overview
-**nfx-stringbuilder** is a modern C++20 library providing efficient string building capabilities with zero heap allocations for small strings (≤256 bytes). Designed for applications requiring high-performance string concatenation with minimal allocations, it features Small Buffer Optimization (SBO), comprehensive type support, and C++20 std::format integration.
+**nfx-stringbuilder** is a modern C++ library providing efficient string building capabilities with zero heap allocations for small strings (≤256 bytes). Designed for applications requiring high-performance string concatenation with minimal allocations, it features Small Buffer Optimization (SBO) and comprehensive type support. It builds with **C++17 or C++20**: the core API is fully available under C++17, while C++20 additionally enables the `std::format`-based `format()` method and the `std::formatter` specialization.
## Key Features
@@ -23,8 +23,8 @@
- **Fluent API**: Method chaining and stream operators (`<<`) for natural concatenation
- **Variadic append()**: Batch multiple arguments in a single call for optimal performance
- **Type Support**: Strings, string_view, C-strings, characters, and numeric types (int8/16/32/64, uint8/16/32/64, float, double)
-- **C++20 std::format Integration**: Template `format()` method for modern formatting
-- **std::formatter Specializations**: Zero-copy integration with `std::format` for StringBuilder
+- **C++20 std::format Integration**: Template `format()` method for modern formatting *(requires C++20)*
+- **std::formatter Specializations**: Zero-copy integration with `std::format` for StringBuilder *(requires C++20)*
- **Capacity Hints**: Pre-allocate buffers with constructor parameter for optimal performance
- **Direct Buffer Access**: High-performance operations without wrappers
- **Iterator Support**: Range-based for loops and STL algorithms
@@ -57,12 +57,21 @@
### Requirements
-- C++20 compatible compiler:
+- C++17 or C++20 compatible compiler:
- **GCC 14+** (14.2.0 tested)
- **Clang 18+** (19.1.7 tested)
- **MSVC 2022+** (19.44+ tested)
- CMake 3.20 or higher
+> **C++ standard:** The library defaults to building with C++20. Select the
+> standard explicitly with `-DNFX_STRINGBUILDER_CXX_STANDARD=17` or `=20`.
+> Under C++17 the entire API is available except the `std::format`-based
+> `format()` method and the `std::formatter` specialization,
+> which require C++20. The exported `nfx-stringbuilder::static` and
+> `nfx-stringbuilder::nfx-stringbuilder` targets propagate a minimum
+> requirement of C++17 to consumers, who may compile at a higher standard than
+> the library was built with.
+
### CMake Integration
```cmake
@@ -70,6 +79,9 @@
option(NFX_STRINGBUILDER_BUILD_STATIC "Build static library" ON )
option(NFX_STRINGBUILDER_BUILD_SHARED "Build shared library" OFF)
+# --- Language standard (17 or 20; C++20 enables std::format APIs) ---
+set(NFX_STRINGBUILDER_CXX_STANDARD "20" CACHE STRING "C++ standard used to build nfx-stringbuilder (17 or 20)")
+
# --- Build components ---
option(NFX_STRINGBUILDER_BUILD_TESTS "Build tests" OFF)
option(NFX_STRINGBUILDER_BUILD_SAMPLES "Build samples" OFF)
diff --git a/benchmark/BM_Comparative.cpp b/benchmark/BM_Comparative.cpp
index d257dda..59c1750 100644
--- a/benchmark/BM_Comparative.cpp
+++ b/benchmark/BM_Comparative.cpp
@@ -42,7 +42,9 @@
# include
#endif
-#include
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
+# include
+#endif
#include
namespace nfx::string::benchmark
@@ -545,9 +547,11 @@ namespace nfx::string::benchmark
for( auto _ : state )
{
(void)_;
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
std::string result =
std::format( "User {} (ID: {}) scored {:.2f} points at {}", "Alice", 42, 95.75, "2024-01-15 10:30:00" );
::benchmark::DoNotOptimize( result );
+#endif
}
}
@@ -582,11 +586,13 @@ namespace nfx::string::benchmark
for( auto _ : state )
{
(void)_;
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
StringBuilder builder;
builder.append( std::format(
"User {} (ID: {}) scored {:.2f} points at {}", "Alice", 42, 95.75, "2024-01-15 10:30:00" ) );
std::string result = builder.toString();
::benchmark::DoNotOptimize( result );
+#endif
}
}
diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt
index 8b5b69e..107a331 100644
--- a/benchmark/CMakeLists.txt
+++ b/benchmark/CMakeLists.txt
@@ -211,7 +211,7 @@ foreach(benchmark_source ${benchmark_sources})
set_target_properties(${benchmark_target_name}
PROPERTIES
- CXX_STANDARD 20
+ CXX_STANDARD ${NFX_STRINGBUILDER_CXX_STANDARD}
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
POSITION_INDEPENDENT_CODE ON
diff --git a/cmake/nfxStringBuilderBuildConfig.cmake b/cmake/nfxStringBuilderBuildConfig.cmake
index 422ddae..5fc0c1d 100644
--- a/cmake/nfxStringBuilderBuildConfig.cmake
+++ b/cmake/nfxStringBuilderBuildConfig.cmake
@@ -30,6 +30,19 @@ if(NOT NFX_STRINGBUILDER_BUILD_STATIC AND NOT NFX_STRINGBUILDER_BUILD_SHARED)
endif()
endif()
+# --- Validate the requested C++ standard ---
+if(NOT NFX_STRINGBUILDER_CXX_STANDARD MATCHES "^(17|20)$")
+ message(FATAL_ERROR
+ "NFX_STRINGBUILDER_CXX_STANDARD must be either 17 or 20 "
+ "(got '${NFX_STRINGBUILDER_CXX_STANDARD}').")
+endif()
+
+if(NFX_STRINGBUILDER_CXX_STANDARD STREQUAL "17")
+ message(STATUS "nfx-stringbuilder: Building with C++17 (std::format API disabled)")
+else()
+ message(STATUS "nfx-stringbuilder: Building with C++${NFX_STRINGBUILDER_CXX_STANDARD}")
+endif()
+
#----------------------------------------------
# Multi-config generator setup
#----------------------------------------------
diff --git a/cmake/nfxStringBuilderTargets.cmake b/cmake/nfxStringBuilderTargets.cmake
index 9c7ce55..4af43d9 100644
--- a/cmake/nfxStringBuilderTargets.cmake
+++ b/cmake/nfxStringBuilderTargets.cmake
@@ -65,7 +65,7 @@ function(configure_target target_name)
# --- Properties ---
set_target_properties(${target_name}
PROPERTIES
- CXX_STANDARD 20
+ CXX_STANDARD ${NFX_STRINGBUILDER_CXX_STANDARD}
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
DEBUG_POSTFIX "-d"
@@ -74,6 +74,12 @@ function(configure_target target_name)
SOVERSION ${PROJECT_VERSION_MAJOR}
)
+ # --- Public language requirement ---
+ # Propagate a minimum of C++17 to consumers of the static/shared targets.
+ # The headers compile under both C++17 and C++20; consumers may build at a
+ # higher standard than the library was compiled with.
+ target_compile_features(${target_name} PUBLIC cxx_std_17)
+
# --- Enable specific CPU features ---
if(NFX_STRINGBUILDER_ENABLE_SIMD)
include(CheckCXXSourceCompiles)
diff --git a/include/nfx/detail/string/StringBuilder.inl b/include/nfx/detail/string/StringBuilder.inl
index 3393d04..74db4dc 100644
--- a/include/nfx/detail/string/StringBuilder.inl
+++ b/include/nfx/detail/string/StringBuilder.inl
@@ -112,7 +112,7 @@ namespace nfx::string
inline void StringBuilder::reserve( size_t newCapacity )
{
- if( newCapacity > m_capacity ) [[likely]]
+ if( newCapacity > m_capacity ) NFX_STRINGBUILDER_LIKELY
{
ensureCapacity( newCapacity );
}
@@ -135,7 +135,7 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::string_view str )
{
- if( str.empty() ) [[unlikely]]
+ if( str.empty() ) NFX_STRINGBUILDER_UNLIKELY
{
return *this;
}
@@ -143,7 +143,7 @@ namespace nfx::string
const size_t len = str.size();
const size_t newSize = m_size + len;
- if( newSize > m_capacity ) [[unlikely]]
+ if( newSize > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( newSize );
}
@@ -156,7 +156,7 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( const char* str )
{
- if( str ) [[likely]]
+ if( str ) NFX_STRINGBUILDER_LIKELY
{
return append( std::string_view{ str, strlen( str ) } );
}
@@ -172,7 +172,7 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( char c )
{
- if( m_size + 1 > m_capacity ) [[unlikely]]
+ if( m_size + 1 > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + 1 );
}
@@ -182,12 +182,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::int8_t value )
{
- if( m_size + INT8_MAX_DIGITS > m_capacity ) [[unlikely]]
+ if( m_size + INT8_MAX_DIGITS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + INT8_MAX_DIGITS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, static_cast( value ) );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -196,12 +196,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::uint8_t value )
{
- if( m_size + UINT8_MAX_DIGITS > m_capacity ) [[unlikely]]
+ if( m_size + UINT8_MAX_DIGITS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + UINT8_MAX_DIGITS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, static_cast( value ) );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -210,12 +210,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::int16_t value )
{
- if( m_size + INT16_MAX_DIGITS > m_capacity ) [[unlikely]]
+ if( m_size + INT16_MAX_DIGITS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + INT16_MAX_DIGITS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -224,12 +224,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::uint16_t value )
{
- if( m_size + UINT16_MAX_DIGITS > m_capacity ) [[unlikely]]
+ if( m_size + UINT16_MAX_DIGITS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + UINT16_MAX_DIGITS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -238,12 +238,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::int32_t value )
{
- if( m_size + INT32_MAX_DIGITS > m_capacity ) [[unlikely]]
+ if( m_size + INT32_MAX_DIGITS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + INT32_MAX_DIGITS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -252,12 +252,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::uint32_t value )
{
- if( m_size + UINT32_MAX_DIGITS > m_capacity ) [[unlikely]]
+ if( m_size + UINT32_MAX_DIGITS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + UINT32_MAX_DIGITS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -266,12 +266,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::int64_t value )
{
- if( m_size + INT64_MAX_DIGITS > m_capacity ) [[unlikely]]
+ if( m_size + INT64_MAX_DIGITS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + INT64_MAX_DIGITS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -280,12 +280,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( std::uint64_t value )
{
- if( m_size + UINT64_MAX_DIGITS > m_capacity ) [[unlikely]]
+ if( m_size + UINT64_MAX_DIGITS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + UINT64_MAX_DIGITS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -294,12 +294,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( float value )
{
- if( m_size + FLOAT_MAX_CHARS > m_capacity ) [[unlikely]]
+ if( m_size + FLOAT_MAX_CHARS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + FLOAT_MAX_CHARS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -308,12 +308,12 @@ namespace nfx::string
inline StringBuilder& StringBuilder::append( double value )
{
- if( m_size + DOUBLE_MAX_CHARS > m_capacity ) [[unlikely]]
+ if( m_size + DOUBLE_MAX_CHARS > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + DOUBLE_MAX_CHARS );
}
auto [ptr, ec] = std::to_chars( m_buffer + m_size, m_buffer + m_capacity, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
m_size = ptr - m_buffer;
}
@@ -329,7 +329,7 @@ namespace nfx::string
const size_t len = str.size();
const size_t newSize = m_size + len + 1; // +1 for newline
- if( newSize > m_capacity ) [[unlikely]]
+ if( newSize > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( newSize );
}
@@ -354,7 +354,7 @@ namespace nfx::string
inline StringBuilder& StringBuilder::appendLine( const char* str )
{
- if( str ) [[likely]]
+ if( str ) NFX_STRINGBUILDER_LIKELY
{
return appendLine( std::string_view{ str, strlen( str ) } );
}
@@ -383,7 +383,7 @@ namespace nfx::string
inline StringBuilder& StringBuilder::prepend( char c )
{
- if( m_size + 1 > m_capacity ) [[unlikely]]
+ if( m_size + 1 > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( m_size + 1 );
}
@@ -405,7 +405,7 @@ namespace nfx::string
{
char buffer[INT8_MAX_DIGITS];
auto [ptr, ec] = std::to_chars( buffer, buffer + INT8_MAX_DIGITS, static_cast( value ) );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -416,7 +416,7 @@ namespace nfx::string
{
char buffer[UINT8_MAX_DIGITS];
auto [ptr, ec] = std::to_chars( buffer, buffer + UINT8_MAX_DIGITS, static_cast( value ) );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -427,7 +427,7 @@ namespace nfx::string
{
char buffer[INT16_MAX_DIGITS];
auto [ptr, ec] = std::to_chars( buffer, buffer + INT16_MAX_DIGITS, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -438,7 +438,7 @@ namespace nfx::string
{
char buffer[UINT16_MAX_DIGITS];
auto [ptr, ec] = std::to_chars( buffer, buffer + UINT16_MAX_DIGITS, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -449,7 +449,7 @@ namespace nfx::string
{
char buffer[INT32_MAX_DIGITS];
auto [ptr, ec] = std::to_chars( buffer, buffer + INT32_MAX_DIGITS, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -460,7 +460,7 @@ namespace nfx::string
{
char buffer[UINT32_MAX_DIGITS];
auto [ptr, ec] = std::to_chars( buffer, buffer + UINT32_MAX_DIGITS, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -471,7 +471,7 @@ namespace nfx::string
{
char buffer[INT64_MAX_DIGITS];
auto [ptr, ec] = std::to_chars( buffer, buffer + INT64_MAX_DIGITS, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -482,7 +482,7 @@ namespace nfx::string
{
char buffer[UINT64_MAX_DIGITS];
auto [ptr, ec] = std::to_chars( buffer, buffer + UINT64_MAX_DIGITS, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -493,7 +493,7 @@ namespace nfx::string
{
char buffer[FLOAT_MAX_CHARS];
auto [ptr, ec] = std::to_chars( buffer, buffer + FLOAT_MAX_CHARS, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -504,7 +504,7 @@ namespace nfx::string
{
char buffer[DOUBLE_MAX_CHARS];
auto [ptr, ec] = std::to_chars( buffer, buffer + DOUBLE_MAX_CHARS, value );
- if( ec == std::errc() ) [[likely]]
+ if( ec == std::errc() ) NFX_STRINGBUILDER_LIKELY
{
return prepend( std::string_view{ buffer, static_cast( ptr - buffer ) } );
}
@@ -615,12 +615,14 @@ namespace nfx::string
// Formatting operations
//----------------------------------------------
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
template
inline StringBuilder& StringBuilder::format( std::format_string fmt, Args&&... args )
{
std::format_to( std::back_inserter( *this ), fmt, std::forward( args )... );
return *this;
}
+#endif // NFX_STRINGBUILDER_HAS_STD_FORMAT
//----------------------------------------------
// Join operations
@@ -742,6 +744,7 @@ namespace nfx::string
}
} // namespace nfx::string
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
namespace std
{
//=====================================================================
@@ -762,3 +765,4 @@ namespace std
}
};
} // namespace std
+#endif // NFX_STRINGBUILDER_HAS_STD_FORMAT
diff --git a/include/nfx/string/StringBuilder.h b/include/nfx/string/StringBuilder.h
index 3fdb93a..416d002 100644
--- a/include/nfx/string/StringBuilder.h
+++ b/include/nfx/string/StringBuilder.h
@@ -71,11 +71,29 @@
#pragma once
#include
-#include
#include
#include
#include
+#if defined( __has_include )
+# if __has_include( )
+# include
+# endif
+#endif
+
+/**
+ * @def NFX_STRINGBUILDER_HAS_STD_FORMAT
+ * @brief Defined to 1 when C++20 std::format support is available
+ * @details std::format requires C++20. When building under C++17 (or with a
+ * standard library that lacks ), the format() method and the
+ * std::formatter specialization are disabled, while the rest of the
+ * StringBuilder API remains fully available.
+ */
+#if defined( __cpp_lib_format ) && __cpp_lib_format >= 201907L
+# include
+# define NFX_STRINGBUILDER_HAS_STD_FORMAT 1
+#endif
+
/**
* @def NFX_STRINGBUILDER_FORCE_INLINE
* @brief Cross-platform forced inline macro for critical hot-path methods
@@ -88,6 +106,32 @@
# define NFX_STRINGBUILDER_FORCE_INLINE inline
#endif
+/**
+ * @def NFX_STRINGBUILDER_LIKELY
+ * @def NFX_STRINGBUILDER_UNLIKELY
+ * @brief Branch-prediction hint attributes (C++20 [[likely]]/[[unlikely]])
+ * @details Expand to the C++20 attributes when supported and to nothing under
+ * C++17, keeping hot-path branch hints warning-free across standards.
+ * The language-version check is required because some compilers report
+ * __has_cpp_attribute(likely) as available in C++17 as an extension.
+ */
+#if defined( _MSVC_LANG )
+# define NFX_STRINGBUILDER_CPLUSPLUS _MSVC_LANG
+#else
+# define NFX_STRINGBUILDER_CPLUSPLUS __cplusplus
+#endif
+
+#if NFX_STRINGBUILDER_CPLUSPLUS >= 202002L && defined( __has_cpp_attribute )
+# if __has_cpp_attribute( likely ) >= 201803L
+# define NFX_STRINGBUILDER_LIKELY [[likely]]
+# define NFX_STRINGBUILDER_UNLIKELY [[unlikely]]
+# endif
+#endif
+#if !defined( NFX_STRINGBUILDER_LIKELY )
+# define NFX_STRINGBUILDER_LIKELY
+# define NFX_STRINGBUILDER_UNLIKELY
+#endif
+
namespace nfx::string
{
//=====================================================================
@@ -676,6 +720,7 @@ namespace nfx::string
// Formatting operations
//----------------------------------------------
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
/**
* @brief Format and append text using std::format
* @tparam Args Types of the formatting arguments
@@ -685,9 +730,11 @@ namespace nfx::string
* @details Provides type-safe formatting directly into the builder without intermediate allocations.
* Uses std::format_to with back_inserter for optimal performance.
* Example: builder.format("User {} (ID: {}) logged in at {}", name, userId, timestamp);
+ * @note Requires C++20 std::format support (NFX_STRINGBUILDER_HAS_STD_FORMAT).
*/
template
inline StringBuilder& format( std::format_string fmt, Args&&... args );
+#endif // NFX_STRINGBUILDER_HAS_STD_FORMAT
//----------------------------------------------
// String conversion
diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt
index d6790f5..293fcc4 100644
--- a/samples/CMakeLists.txt
+++ b/samples/CMakeLists.txt
@@ -89,7 +89,7 @@ foreach(sample_source ${sample_sources})
set_target_properties(${sample_target_name}
PROPERTIES
- CXX_STANDARD 20
+ CXX_STANDARD ${NFX_STRINGBUILDER_CXX_STANDARD}
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
POSITION_INDEPENDENT_CODE ON
diff --git a/samples/Sample_StringBuilder.cpp b/samples/Sample_StringBuilder.cpp
index 3eadb2d..14321dc 100644
--- a/samples/Sample_StringBuilder.cpp
+++ b/samples/Sample_StringBuilder.cpp
@@ -34,13 +34,16 @@
#include
#include
#include
-#include
#include
#include
#include
#include
#include
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
+# include
+#endif
+
//=====================================================================
// Helper function: Calculate NMEA-0183 checksum
//=====================================================================
@@ -238,6 +241,7 @@ int main()
std::cout << "8. std::format integration with StringBuilder\n";
std::cout << "-----------------------------------------------\n";
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
StringBuilder builder;
builder.append( "Formatted content" );
@@ -250,6 +254,9 @@ int main()
std::cout << std::format( "Debug: buffer size={}, content={}\n", builder.size(), builder );
std::cout << "Note: Zero-copy formatting via std::formatter specialization\n";
+#else
+ std::cout << "Note: std::format integration requires C++20 (disabled in this build)\n";
+#endif
std::cout << "\n";
}
@@ -260,6 +267,7 @@ int main()
std::cout << "9. std::format integration with StringBuilder\n";
std::cout << "----------------------------------------------\n";
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
StringBuilder builder;
builder.append( "Building..." );
@@ -269,6 +277,9 @@ int main()
std::cout << std::format( "After append: {}\n", builder );
std::cout << "Note: Useful for debugging string construction process\n";
+#else
+ std::cout << "Note: std::format integration requires C++20 (disabled in this build)\n";
+#endif
std::cout << "\n";
}
@@ -317,6 +328,7 @@ int main()
std::cout << "11. Type-safe formatting with format() method\n";
std::cout << "----------------------------------------------\n";
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
StringBuilder builder;
std::string username = "Alice";
@@ -345,6 +357,9 @@ int main()
std::cout << "Chaining: " << builder.toString() << "\n";
std::cout << "Note: format() uses std::format internally for zero-overhead type-safe formatting\n";
+#else
+ std::cout << "Note: std::format-based formatting requires C++20 (disabled in this build)\n";
+#endif
std::cout << "\n";
}
diff --git a/src/StringBuilder.cpp b/src/StringBuilder.cpp
index 2085faa..8a86cbf 100644
--- a/src/StringBuilder.cpp
+++ b/src/StringBuilder.cpp
@@ -70,7 +70,7 @@ namespace nfx::string
m_capacity{ other.m_capacity },
m_onHeap{ other.m_onHeap }
{
- if( m_onHeap ) [[likely]]
+ if( m_onHeap ) NFX_STRINGBUILDER_LIKELY
{
m_heapBuffer = std::make_unique( m_capacity );
m_buffer = m_heapBuffer.get();
@@ -89,7 +89,7 @@ namespace nfx::string
m_capacity{ other.m_capacity },
m_onHeap{ other.m_onHeap }
{
- if( !m_onHeap ) [[likely]]
+ if( !m_onHeap ) NFX_STRINGBUILDER_LIKELY
{
// Fast move: just copy stack buffer content without clearing source
std::memcpy( m_stackBuffer, other.m_stackBuffer, m_size );
@@ -108,7 +108,7 @@ namespace nfx::string
StringBuilder& StringBuilder::operator=( const StringBuilder& other )
{
- if( this != &other ) [[likely]]
+ if( this != &other ) NFX_STRINGBUILDER_LIKELY
{
if( other.m_onHeap )
{
@@ -141,7 +141,7 @@ namespace nfx::string
StringBuilder& StringBuilder::operator=( StringBuilder&& other ) noexcept
{
- if( this != &other ) [[likely]]
+ if( this != &other ) NFX_STRINGBUILDER_LIKELY
{
m_heapBuffer = std::move( other.m_heapBuffer );
m_size = other.m_size;
@@ -172,7 +172,7 @@ namespace nfx::string
StringBuilder& StringBuilder::prepend( std::string_view str )
{
- if( str.empty() ) [[unlikely]]
+ if( str.empty() ) NFX_STRINGBUILDER_UNLIKELY
{
return *this;
}
@@ -180,7 +180,7 @@ namespace nfx::string
const size_t len = str.size();
const size_t newSize = m_size + len;
- if( newSize > m_capacity ) [[unlikely]]
+ if( newSize > m_capacity ) NFX_STRINGBUILDER_UNLIKELY
{
ensureCapacity( newSize );
}
@@ -188,7 +188,7 @@ namespace nfx::string
char* buf = m_buffer;
// Shift existing content to the right using memmove (handles overlap)
- if( m_size > 0 ) [[likely]]
+ if( m_size > 0 ) NFX_STRINGBUILDER_LIKELY
{
std::memmove( buf + len, buf, m_size );
}
@@ -226,7 +226,7 @@ namespace nfx::string
{
// Transition from stack to heap
m_heapBuffer = std::make_unique( newCapacity );
- if( m_size > 0 ) [[likely]]
+ if( m_size > 0 ) NFX_STRINGBUILDER_LIKELY
{
std::memcpy( m_heapBuffer.get(), m_stackBuffer, m_size );
}
@@ -238,7 +238,7 @@ namespace nfx::string
{
// Expand existing heap buffer
auto newBuffer = std::make_unique( newCapacity );
- if( m_size > 0 ) [[likely]]
+ if( m_size > 0 ) NFX_STRINGBUILDER_LIKELY
{
std::memcpy( newBuffer.get(), m_heapBuffer.get(), m_size );
}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 7d1c2f0..c97eda0 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -144,7 +144,7 @@ foreach(test_source ${test_sources})
set_target_properties(${test_target_name}
PROPERTIES
- CXX_STANDARD 20
+ CXX_STANDARD ${NFX_STRINGBUILDER_CXX_STANDARD}
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
POSITION_INDEPENDENT_CODE ON
diff --git a/test/Tests_StringBuilder.cpp b/test/Tests_StringBuilder.cpp
index 16dd17f..77b18da 100644
--- a/test/Tests_StringBuilder.cpp
+++ b/test/Tests_StringBuilder.cpp
@@ -334,7 +334,7 @@ namespace nfx::string::test
TEST( StringBuilder, AppendInt32 )
{
StringBuilder builder;
- builder.append( static_cast( -2147483648 ) );
+ builder.append( static_cast( -2147483647 - 1 ) );
EXPECT_EQ( builder.toString(), "-2147483648" );
builder.clear();
@@ -765,6 +765,7 @@ namespace nfx::string::test
// Format operator
//----------------------------------------------
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
TEST( StringBuilder, FormatBasic )
{
StringBuilder builder;
@@ -792,6 +793,7 @@ namespace nfx::string::test
builder.append( "Start: " ).format( "{}", 100 ).append( " End" );
EXPECT_EQ( builder.toString(), "Start: 100 End" );
}
+#endif // NFX_STRINGBUILDER_HAS_STD_FORMAT
//----------------------------------------------
// String conversion
@@ -944,8 +946,13 @@ namespace nfx::string::test
TEST( StringBuilder, ComplexChaining )
{
StringBuilder builder;
+#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
( ( builder.append( "Start" ).append( " middle " ).append( 42 ).format( " formatted: {}", "text" ) )
.append( " end" ) );
+#else
+ ( ( builder.append( "Start" ).append( " middle " ).append( 42 ).append( " formatted: text" ) )
+ .append( " end" ) );
+#endif
std::string result = builder.toString();
EXPECT_NE( result.find( "Start" ), std::string::npos );