Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions .github/workflows/build-linux-clang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: |
Expand All @@ -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 \
Expand All @@ -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
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/build-linux-gcc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: |
Expand All @@ -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 \
Expand All @@ -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
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/build-windows-mingw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 \
Expand All @@ -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
Expand Down
14 changes: 9 additions & 5 deletions .github/workflows/build-windows-msvc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: |
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://github.com/nfx-libs/nfx-stringbuilder/blob/main/LICENSE) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/nfx-libs/nfx-stringbuilder?style=flat-square)](https://github.com/nfx-libs/nfx-stringbuilder/releases) [![GitHub tag (latest by date)](https://img.shields.io/github/tag/nfx-libs/nfx-stringbuilder?style=flat-square)](https://github.com/nfx-libs/nfx-stringbuilder/tags)<br/>

![C++20](https://img.shields.io/badge/C%2B%2B-20-blue?style=flat-square) ![CMake](https://img.shields.io/badge/CMake-3.20%2B-green.svg?style=flat-square) ![Cross Platform](https://img.shields.io/badge/Platform-Linux_Windows-lightgrey?style=flat-square)
![C++17/20](https://img.shields.io/badge/C%2B%2B-17%2F20-blue?style=flat-square) ![CMake](https://img.shields.io/badge/CMake-3.20%2B-green.svg?style=flat-square) ![Cross Platform](https://img.shields.io/badge/Platform-Linux_Windows-lightgrey?style=flat-square)

<!-- CI/CD Status -->

[![Linux GCC](https://img.shields.io/github/actions/workflow/status/nfx-libs/nfx-stringbuilder/build-linux-gcc.yml?branch=main&label=Linux%20GCC&style=flat-square)](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/build-linux-gcc.yml) [![Linux Clang](https://img.shields.io/github/actions/workflow/status/nfx-libs/nfx-stringbuilder/build-linux-clang.yml?branch=main&label=Linux%20Clang&style=flat-square)](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/build-linux-clang.yml) [![Windows MinGW](https://img.shields.io/github/actions/workflow/status/nfx-libs/nfx-stringbuilder/build-windows-mingw.yml?branch=main&label=Windows%20MinGW&style=flat-square)](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/build-windows-mingw.yml) [![Windows MSVC](https://img.shields.io/github/actions/workflow/status/nfx-libs/nfx-stringbuilder/build-windows-msvc.yml?branch=main&label=Windows%20MSVC&style=flat-square)](https://github.com/nfx-libs/nfx-stringbuilder/actions/workflows/build-windows-msvc.yml) [![CodeQL](https://img.shields.io/github/actions/workflow/status/nfx-libs/nfx-stringbuilder/codeql.yml?branch=main&label=CodeQL&style=flat-square)](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

Expand All @@ -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
Expand Down Expand Up @@ -57,19 +57,31 @@

### 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<StringBuilder>` 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
# --- Library build types ---
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)
Expand Down
8 changes: 7 additions & 1 deletion benchmark/BM_Comparative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
# include <absl/strings/str_join.h>
#endif

#include <format>
#if defined( NFX_STRINGBUILDER_HAS_STD_FORMAT )
# include <format>
#endif
#include <sstream>

namespace nfx::string::benchmark
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
}
}

Expand Down
2 changes: 1 addition & 1 deletion benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions cmake/nfxStringBuilderBuildConfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
#----------------------------------------------
Expand Down
8 changes: 7 additions & 1 deletion cmake/nfxStringBuilderTargets.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down
Loading
Loading