Add an experimental basic_static_cstring template in example/

This introduces an alternative to basic_static_string designed for use
in POD types: Trivially copyable, having a sizeof == N + 1, with no
embedded NULs.

Placed in example/ to gather user feedback before committing to a public
API. See issue #23.
This commit is contained in:
Gennaro Prota
2025-12-19 12:32:50 +01:00
committed by Gennaro Prota
parent 0c5e5b8e58
commit e9313dc331
5 changed files with 1300 additions and 0 deletions

156
.github/workflows/example.yml vendored Normal file
View File

@@ -0,0 +1,156 @@
#
# Copyright (c) 2025 Gennaro Prota (gennaro dot prota at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/static_string
#
name: Example (basic_static_cstring)
on:
pull_request:
push:
branches:
- master
- develop
- bugfix/**
- feature/**
- fix/**
- github/**
- pr/**
paths-ignore:
- LICENSE
- meta/**
- README.md
jobs:
linux:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- { toolset: gcc-13, cxxstd: '20,23' }
- { toolset: gcc-14, cxxstd: '20,23,26' }
- { toolset: clang-17, cxxstd: '20,23' }
- { toolset: clang-18, cxxstd: '20,23,26' }
steps:
- name: Checkout Boost super-project
uses: actions/checkout@v4
with:
repository: boostorg/boost
ref: develop
fetch-depth: 0
- name: Checkout this library
uses: actions/checkout@v4
with:
path: libs/static_string
fetch-depth: 0
- name: Initialize Boost submodules
run: |
git submodule update --init tools/boostdep
python tools/boostdep/depinst/depinst.py --git_args '--jobs 4' static_string
- name: Bootstrap b2
run: ./bootstrap.sh
- name: Generate Boost headers
run: ./b2 headers
- name: Build and run example tests
run: |
./b2 libs/static_string/example/static_cstring \
toolset=${{ matrix.toolset }} \
cxxstd=${{ matrix.cxxstd }} \
variant=debug,release \
-j$(nproc)
macos:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
include:
- { toolset: clang, cxxstd: '20,23' }
steps:
- name: Checkout Boost super-project
uses: actions/checkout@v4
with:
repository: boostorg/boost
ref: develop
fetch-depth: 0
- name: Checkout this library
uses: actions/checkout@v4
with:
path: libs/static_string
fetch-depth: 0
- name: Initialize Boost submodules
run: |
git submodule update --init tools/boostdep
python3 tools/boostdep/depinst/depinst.py --git_args '--jobs 4' static_string
- name: Bootstrap b2
run: ./bootstrap.sh
- name: Generate Boost headers
run: ./b2 headers
- name: Build and run example tests
run: |
./b2 libs/static_string/example/static_cstring \
toolset=${{ matrix.toolset }} \
cxxstd=${{ matrix.cxxstd }} \
variant=debug,release \
-j$(sysctl -n hw.ncpu)
windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
include:
- { toolset: msvc-14.3, cxxstd: '20,latest' }
steps:
- name: Checkout Boost super-project
uses: actions/checkout@v4
with:
repository: boostorg/boost
ref: develop
fetch-depth: 0
- name: Checkout this library
uses: actions/checkout@v4
with:
path: libs/static_string
fetch-depth: 0
- name: Initialize Boost submodules
run: |
git submodule update --init tools/boostdep
python tools/boostdep/depinst/depinst.py --git_args '--jobs 4' static_string
- name: Bootstrap b2
run: .\bootstrap.bat
shell: cmd
- name: Generate Boost headers
run: .\b2 headers
shell: cmd
- name: Build and run example tests
run: |
.\b2 libs/static_string/example/static_cstring ^
toolset=${{ matrix.toolset }} ^
cxxstd=${{ matrix.cxxstd }} ^
variant=debug,release ^
address-model=64
shell: cmd

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2025 Gennaro Prota (gennaro dot prota at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
#
# Official repository: https://github.com/boostorg/static_string
#
import testing ;
project
: requirements
<include>../../include
<warnings>extra
<cxxstd>20
;
run static_cstring_test.cpp ;

View File

@@ -0,0 +1,13 @@
This directory contains an experimental implementation of `basic_static_cstring`, which differs from `basic_static_string` in the following ways:
| | `basic_static_cstring` | `basic_static_string` |
|---------------------|------------------------|-----------------------|
| Layout | `sizeof == N + 1` | Has size member |
| Embedded NULs | Not supported | Supported |
| Trivially copyable | Yes | No |
Additionally, when `N <= UCHAR_MAX`, `basic_static_cstring` employs an optimization that avoids calling `std::strlen()` to compute the size.
This work stems from [boostorg/static_string#23](https://github.com/boostorg/static_string/issues/23).
If you believe `basic_static_cstring` should become part of the public API, please share your feedback on the Boost mailing list.

View File

@@ -0,0 +1,441 @@
//
// Copyright (c) 2025 Gennaro Prota (gennaro dot prota at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/static_string
//
#ifndef BOOST_STATIC_STRING_STATIC_CSTRING_HPP
#define BOOST_STATIC_STRING_STATIC_CSTRING_HPP
#include <boost/static_string/config.hpp>
#include <algorithm>
#include <climits>
#include <cstddef>
#include <ostream>
#include <string>
#include <string_view>
#include <stdexcept>
#include <type_traits>
namespace boost {
namespace static_strings {
namespace detail {
// Primary template: No remaining-capacity trick; uses traits::length() for length.
template<std::size_t N, typename CharT, typename Traits, bool UseRemaining>
class static_cstring_base
{
public:
using traits_type = Traits;
using value_type = CharT;
using size_type = std::size_t;
value_type data_[N + 1]{};
constexpr size_type get_size() const noexcept
{
return traits_type::length(data_);
}
constexpr void set_size(size_type sz) noexcept
{
data_[sz] = value_type{};
}
// Defaulted comparisons for structural type support.
constexpr bool operator==(const static_cstring_base&) const noexcept = default;
constexpr auto operator<=>(const static_cstring_base&) const noexcept = default;
};
// Specialization for N <= UCHAR_MAX: Uses remaining-capacity trick.
template<std::size_t N, typename CharT, typename Traits>
class static_cstring_base<N, CharT, Traits, true>
{
public:
using traits_type = Traits;
using value_type = CharT;
using size_type = std::size_t;
value_type data_[N + 1]{};
constexpr size_type get_size() const noexcept
{
return N - static_cast<unsigned char>(data_[N]);
}
constexpr void set_size(size_type sz) noexcept
{
data_[sz] = value_type{};
data_[N] = static_cast<value_type>(N - sz);
}
// Defaulted comparisons for structural type support.
constexpr bool operator==(const static_cstring_base&) const noexcept = default;
constexpr auto operator<=>(const static_cstring_base&) const noexcept = default;
};
} // namespace detail
template<std::size_t N, typename CharT = char, typename Traits = std::char_traits<CharT>>
class basic_static_cstring
: public detail::static_cstring_base<N, CharT, Traits, (N <= UCHAR_MAX)>
{
public:
using base = detail::static_cstring_base<N, CharT, Traits, (N <= UCHAR_MAX)>;
using base::data_;
using base::get_size;
using base::set_size;
// Member types
using traits_type = Traits;
using value_type = CharT;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using iterator = pointer;
using const_iterator = const_pointer;
static constexpr size_type npos = static_cast<size_type>(-1);
static constexpr size_type static_capacity = N;
// Constructors.
constexpr basic_static_cstring() noexcept
{
set_size(0);
}
constexpr basic_static_cstring(const CharT* s)
{
assign(s);
}
constexpr basic_static_cstring(const CharT* s, size_type count)
{
assign(s, count);
}
constexpr basic_static_cstring(size_type count, CharT ch)
{
assign(count, ch);
}
template<std::size_t M>
constexpr basic_static_cstring(const CharT (&arr)[M])
{
static_assert(M <= N + 1, "String literal too long for static_cstring");
assign(arr, M - 1);
}
constexpr size_type size() const noexcept
{
return get_size();
}
constexpr size_type length() const noexcept
{
return size();
}
constexpr bool empty() const noexcept
{
return data_[0] == value_type{};
}
static constexpr size_type max_size() noexcept
{
return N;
}
static constexpr size_type capacity() noexcept
{
return N;
}
// Element access.
constexpr reference operator[](size_type pos) noexcept
{
return data_[pos];
}
constexpr const_reference operator[](size_type pos) const noexcept
{
return data_[pos];
}
constexpr reference at(size_type pos)
{
if (pos >= size())
{
throw std::out_of_range("static_cstring::at");
}
return data_[pos];
}
constexpr const_reference at(size_type pos) const
{
if (pos >= size())
{
throw std::out_of_range("static_cstring::at");
}
return data_[pos];
}
constexpr reference front() noexcept
{
BOOST_STATIC_STRING_ASSERT(!empty());
return data_[0];
}
constexpr const_reference front() const noexcept
{
BOOST_STATIC_STRING_ASSERT(!empty());
return data_[0];
}
constexpr reference back() noexcept
{
BOOST_STATIC_STRING_ASSERT(!empty());
return data_[size() - 1];
}
constexpr const_reference back() const noexcept
{
BOOST_STATIC_STRING_ASSERT(!empty());
return data_[size() - 1];
}
constexpr pointer data() noexcept
{
return data_;
}
constexpr const_pointer data() const noexcept
{
return data_;
}
constexpr const_pointer c_str() const noexcept
{
return data_;
}
// Iterators.
constexpr iterator begin() noexcept
{
return data_;
}
constexpr const_iterator begin() const noexcept
{
return data_;
}
constexpr const_iterator cbegin() const noexcept
{
return data_;
}
constexpr iterator end() noexcept
{
return data_ + size();
}
constexpr const_iterator end() const noexcept
{
return data_ + size();
}
constexpr const_iterator cend() const noexcept
{
return data_ + size();
}
// Modifiers.
constexpr void clear() noexcept
{
set_size(0);
}
constexpr basic_static_cstring& assign(const CharT* s)
{
return assign(s, traits_type::length(s));
}
constexpr basic_static_cstring& assign(const CharT* s, size_type count)
{
if (count > N)
{
throw std::length_error("static_cstring::assign");
}
traits_type::copy(data_, s, count);
set_size(count);
return *this;
}
constexpr basic_static_cstring& assign(size_type count, CharT ch)
{
if (count > N)
{
throw std::length_error("static_cstring::assign");
}
traits_type::assign(data_, count, ch);
set_size(count);
return *this;
}
constexpr basic_static_cstring& operator=(const CharT* s)
{
return assign(s);
}
constexpr void push_back(CharT ch)
{
const size_type sz = size();
if (sz >= N)
{
throw std::length_error("static_cstring::push_back");
}
data_[sz] = ch;
set_size(sz + 1);
}
constexpr void pop_back() noexcept
{
BOOST_STATIC_STRING_ASSERT(!empty());
set_size(size() - 1);
}
constexpr basic_static_cstring& append(const CharT* s)
{
return append(s, traits_type::length(s));
}
constexpr basic_static_cstring& append(const CharT* s, size_type count)
{
const size_type sz = size();
if (sz + count > N)
{
throw std::length_error("static_cstring::append");
}
traits_type::copy(data_ + sz, s, count);
set_size(sz + count);
return *this;
}
constexpr basic_static_cstring& append(size_type count, CharT ch)
{
const size_type sz = size();
if (sz + count > N)
{
throw std::length_error("static_cstring::append");
}
traits_type::assign(data_ + sz, count, ch);
set_size(sz + count);
return *this;
}
constexpr basic_static_cstring& operator+=(const CharT* s)
{
return append(s);
}
constexpr basic_static_cstring& operator+=(CharT ch)
{
push_back(ch);
return *this;
}
// Comparisons.
constexpr int compare(const basic_static_cstring& other) const noexcept
{
const size_type lhs_sz = size();
const size_type rhs_sz = other.size();
const int result = traits_type::compare(data_, other.data_, (std::min)(lhs_sz, rhs_sz));
return result != 0
? result
: lhs_sz < rhs_sz
? -1
: lhs_sz > rhs_sz
? 1
: 0;
}
constexpr int compare(const CharT* s) const noexcept
{
return compare(basic_static_cstring(s));
}
// Conversions.
constexpr operator std::basic_string_view<CharT, Traits>() const noexcept
{
return {data_, size()};
}
std::basic_string<CharT, Traits> str() const
{
return {data_, size()};
}
// Swap.
constexpr void swap(basic_static_cstring& other) noexcept
{
basic_static_cstring tmp = *this;
*this = other;
other = tmp;
}
// Defaulted comparisons for structural type (C++20).
constexpr bool operator==(const basic_static_cstring&) const noexcept = default;
constexpr auto operator<=>(const basic_static_cstring&) const noexcept = default;
};
#if defined(BOOST_STATIC_STRING_USE_DEDUCT)
// Deduction guide.
template<std::size_t N, typename CharT>
basic_static_cstring(const CharT(&)[N]) -> basic_static_cstring<N - 1, CharT>;
#endif
// Comparison with const CharT*.
template<std::size_t N, typename CharT, typename Traits>
constexpr bool operator==(const basic_static_cstring<N, CharT, Traits>& lhs,
const CharT* rhs) noexcept
{
return lhs.compare(rhs) == 0;
}
template<std::size_t N, typename CharT, typename Traits>
constexpr bool operator==(const CharT* lhs,
const basic_static_cstring<N, CharT, Traits>& rhs) noexcept
{
return rhs.compare(lhs) == 0;
}
// Stream output.
template<std::size_t N, typename CharT, typename Traits>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const basic_static_cstring<N, CharT, Traits>& str)
{
return os << str.c_str();
}
// Alias templates.
template<std::size_t N>
using static_cstring = basic_static_cstring<N, char>;
template<std::size_t N>
using static_wcstring = basic_static_cstring<N, wchar_t>;
}
}
#endif

View File

@@ -0,0 +1,671 @@
//
// Copyright (c) 2025 Gennaro Prota (gennaro dot prota at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/static_string
//
#include "static_cstring.hpp"
#include <boost/core/lightweight_test.hpp>
#include <climits>
#include <cstring>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
namespace boost {
namespace static_strings {
static
void
testCStringSizeGuarantee()
{
// Core guarantee: sizeof is exactly N + 1 (no size member).
static_assert(sizeof(static_cstring<0>) == 1, "");
static_assert(sizeof(static_cstring<1>) == 2, "");
static_assert(sizeof(static_cstring<10>) == 11, "");
static_assert(sizeof(static_cstring<63>) == 64, "");
static_assert(sizeof(static_cstring<64>) == 65, "");
static_assert(sizeof(static_cstring<127>) == 128, "");
static_assert(sizeof(static_cstring<UCHAR_MAX>) == (UCHAR_MAX + 1), "");
static_assert(sizeof(static_cstring<UCHAR_MAX + 1>) == (UCHAR_MAX + 2), "");
static_assert(sizeof(static_cstring<1000>) == 1001, "");
}
static
void
testCStringRemainingCapacityTrick()
{
// Test the remaining-capacity optimization for N <= UCHAR_MAX.
// Full capacity: Last byte is both null terminator AND remaining == 0.
{
static_cstring<5> full("12345");
BOOST_TEST(full.size() == 5);
BOOST_TEST(full.capacity() == 5);
BOOST_TEST(full.data()[5] == '\0');
BOOST_TEST(std::strcmp(full.c_str(), "12345") == 0);
}
// Partial fill: Null terminator at position size, remaining at position N.
{
static_cstring<10> partial("Hi");
BOOST_TEST(partial.size() == 2);
BOOST_TEST(partial.capacity() == 10);
BOOST_TEST(partial.data()[2] == '\0');
BOOST_TEST(static_cast<unsigned char>(partial.data()[10]) == 8);
}
// Empty string.
{
static_cstring<10> empty;
BOOST_TEST(empty.size() == 0);
BOOST_TEST(empty.capacity() == 10);
BOOST_TEST(empty.data()[0] == '\0');
BOOST_TEST(static_cast<unsigned char>(empty.data()[10]) == 10);
}
// Edge case: N == UCHAR_MAX (max for our trick).
{
static_cstring<UCHAR_MAX> large;
large.assign(100, 'x');
BOOST_TEST(large.size() == 100);
BOOST_TEST(large.capacity() == UCHAR_MAX);
BOOST_TEST(static_cast<unsigned char>(large.data()[UCHAR_MAX]) == (UCHAR_MAX - large.size()));
}
}
template<typename S>
struct CStringTypeTraits
{
static_assert(std::is_trivially_copyable<S>::value);
static_assert(std::is_trivially_copy_constructible<S>::value);
static_assert(std::is_trivially_move_constructible<S>::value);
static_assert(std::is_trivially_copy_assignable<S>::value);
static_assert(std::is_trivially_move_assignable<S>::value);
static_assert(std::is_trivially_destructible<S>::value);
};
static
void
testCStringTypeTraits()
{
{
using S = static_cstring<0>;
CStringTypeTraits< S > check;
static_cast<void>(check);
}
{
using S = static_cstring<63>;
CStringTypeTraits< S > check;
static_cast<void>(check);
}
{
using S = static_cstring<300>;
CStringTypeTraits< S > check;
static_cast<void>(check);
}
}
static
void
testCStringConstruct()
{
// Default construction.
{
static_cstring<1> s;
BOOST_TEST(s.empty());
BOOST_TEST(s.size() == 0);
BOOST_TEST(s == "");
BOOST_TEST(*s.end() == 0);
}
// Construct with count and char.
{
static_cstring<4> s1(3, 'x');
BOOST_TEST(!s1.empty());
BOOST_TEST(s1.size() == 3);
BOOST_TEST(s1 == "xxx");
BOOST_TEST(*s1.end() == 0);
BOOST_TEST_THROWS(
(static_cstring<2>(3, 'x')),
std::length_error);
}
// Construct from a C string.
{
static_cstring<5> s1("12345");
BOOST_TEST(s1.size() == 5);
BOOST_TEST(s1 == "12345");
BOOST_TEST(*s1.end() == 0);
BOOST_TEST_THROWS(
(static_cstring<4>("12345")),
std::length_error);
}
// Construct from a C string with count.
{
static_cstring<5> s1("UVXYZ", 3);
BOOST_TEST(s1 == "UVX");
BOOST_TEST(*s1.end() == 0);
}
// Copy construction.
{
static_cstring<5> s1("12345");
static_cstring<5> s2(s1);
BOOST_TEST(s2 == "12345");
BOOST_TEST(*s2.end() == 0);
}
}
static
void
testCStringAssignment()
{
// assign(size_type count, CharT ch).
BOOST_TEST(static_cstring<3>{}.assign(1, '*') == "*");
BOOST_TEST(static_cstring<3>{}.assign(3, '*') == "***");
BOOST_TEST(static_cstring<3>{"abc"}.assign(3, '*') == "***");
BOOST_TEST_THROWS(static_cstring<1>{"a"}.assign(2, '*'), std::length_error);
// assign(CharT const* s, size_type count).
BOOST_TEST(static_cstring<3>{}.assign("abc", 3) == "abc");
BOOST_TEST(static_cstring<3>{"*"}.assign("abc", 3) == "abc");
BOOST_TEST_THROWS(static_cstring<1>{}.assign("abc", 3), std::length_error);
// assign(CharT const* s).
BOOST_TEST(static_cstring<3>{}.assign("abc") == "abc");
BOOST_TEST(static_cstring<3>{"*"}.assign("abc") == "abc");
BOOST_TEST_THROWS(static_cstring<1>{}.assign("abc"), std::length_error);
// operator=(const CharT* s).
{
static_cstring<3> s1;
s1 = "123";
BOOST_TEST(s1 == "123");
BOOST_TEST(*s1.end() == 0);
static_cstring<1> s2;
BOOST_TEST_THROWS(
s2 = "123",
std::length_error);
}
// Copy assignment.
{
static_cstring<3> s1("123");
static_cstring<3> s2;
s2 = s1;
BOOST_TEST(s2 == "123");
BOOST_TEST(*s2.end() == 0);
}
}
static
void
testCStringElements()
{
using ccs3 = static_cstring<3> const;
// at(size_type pos).
BOOST_TEST(static_cstring<3>{"abc"}.at(0) == 'a');
BOOST_TEST(static_cstring<3>{"abc"}.at(2) == 'c');
BOOST_TEST_THROWS(static_cstring<3>{""}.at(0), std::out_of_range);
BOOST_TEST_THROWS(static_cstring<3>{"abc"}.at(4), std::out_of_range);
// at(size_type pos) const.
BOOST_TEST(ccs3{"abc"}.at(0) == 'a');
BOOST_TEST(ccs3{"abc"}.at(2) == 'c');
BOOST_TEST_THROWS(ccs3{""}.at(0), std::out_of_range);
// operator[](size_type pos).
BOOST_TEST(static_cstring<3>{"abc"}[0] == 'a');
BOOST_TEST(static_cstring<3>{"abc"}[2] == 'c');
BOOST_TEST(static_cstring<3>{"abc"}[3] == 0);
BOOST_TEST(static_cstring<3>{""}[0] == 0);
// front() / back().
BOOST_TEST(static_cstring<3>{"abc"}.front() == 'a');
BOOST_TEST(static_cstring<3>{"abc"}.back() == 'c');
// data() / c_str().
{
static_cstring<3> s("123");
BOOST_TEST(std::memcmp(s.data(), "123", 3) == 0);
BOOST_TEST(std::memcmp(s.c_str(), "123\0", 4) == 0);
}
// Modification through element access.
{
static_cstring<5> s("12345");
s[1] = '_';
BOOST_TEST(s == "1_345");
s.front() = 'A';
BOOST_TEST(s == "A_345");
s.back() = 'Z';
BOOST_TEST(s == "A_34Z");
}
}
static
void
testCStringIterators()
{
{
static_cstring<3> s;
BOOST_TEST(std::distance(s.begin(), s.end()) == 0);
s = "123";
BOOST_TEST(std::distance(s.begin(), s.end()) == 3);
}
{
static_cstring<3> const s("123");
BOOST_TEST(std::distance(s.begin(), s.end()) == 3);
BOOST_TEST(std::distance(s.cbegin(), s.cend()) == 3);
}
// Iteration.
{
static_cstring<5> s("hello");
std::string result;
for (const char c : s)
{
result += c;
}
BOOST_TEST(result == "hello");
}
}
static
void
testCStringCapacity()
{
// empty().
BOOST_TEST(static_cstring<0>{}.empty());
BOOST_TEST(static_cstring<1>{}.empty());
BOOST_TEST(!static_cstring<1>{"a"}.empty());
// size().
BOOST_TEST(static_cstring<0>{}.size() == 0);
BOOST_TEST(static_cstring<1>{"a"}.size() == 1);
BOOST_TEST(static_cstring<5>{"abc"}.size() == 3);
// length().
BOOST_TEST(static_cstring<0>{}.length() == 0);
BOOST_TEST(static_cstring<3>{"abc"}.length() == 3);
// max_size().
BOOST_TEST(static_cstring<0>{}.max_size() == 0);
BOOST_TEST(static_cstring<5>{"abc"}.max_size() == 5);
// capacity().
BOOST_TEST(static_cstring<0>{}.capacity() == 0);
BOOST_TEST(static_cstring<5>{"abc"}.capacity() == 5);
}
static
void
testCStringClear()
{
static_cstring<3> s("123");
BOOST_TEST(!s.empty());
s.clear();
BOOST_TEST(s.empty());
BOOST_TEST(s.size() == 0);
BOOST_TEST(*s.end() == 0);
BOOST_TEST(s == "");
}
static
void
testCStringPushPop()
{
// push_back().
{
static_cstring<5> s("abc");
s.push_back('d');
BOOST_TEST(s == "abcd");
BOOST_TEST(s.size() == 4);
BOOST_TEST(*s.end() == 0);
s.push_back('e');
BOOST_TEST(s == "abcde");
BOOST_TEST(s.size() == 5);
BOOST_TEST_THROWS(s.push_back('f'), std::length_error);
}
// pop_back().
{
static_cstring<5> s("abcde");
s.pop_back();
BOOST_TEST(s == "abcd");
BOOST_TEST(s.size() == 4);
BOOST_TEST(*s.end() == 0);
s.pop_back();
s.pop_back();
s.pop_back();
s.pop_back();
BOOST_TEST(s.empty());
}
}
static
void
testCStringAppend()
{
// append(const CharT* s).
{
static_cstring<12> s("Hello");
s.append(", World");
BOOST_TEST(s == "Hello, World");
BOOST_TEST(*s.end() == 0);
}
{
static_cstring<5> s("abc");
BOOST_TEST_THROWS(s.append("def"), std::length_error);
}
// append(const CharT* s, size_type count)
{
static_cstring<10> s("abc");
s.append("defgh", 3);
BOOST_TEST(s == "abcdef");
BOOST_TEST(*s.end() == 0);
}
// append(size_type count, CharT ch)
{
static_cstring<10> s("abc");
s.append(3, 'x');
BOOST_TEST(s == "abcxxx");
BOOST_TEST(*s.end() == 0);
BOOST_TEST_THROWS(s.append(5, 'y'), std::length_error);
}
// operator+=()
{
static_cstring<10> s("abc");
s += "def";
BOOST_TEST(s == "abcdef");
s += 'g';
BOOST_TEST(s == "abcdefg");
BOOST_TEST(*s.end() == 0);
}
}
static
void
testCStringComparison()
{
// operator==() / operator!=().
{
static_cstring<10> a("abc");
static_cstring<10> b("abc");
static_cstring<10> c("abd");
BOOST_TEST(a == b);
BOOST_TEST(!(a == c));
BOOST_TEST(a != c);
BOOST_TEST(!(a != b));
}
// operator<(), <=(), >(), >=().
{
static_cstring<10> a("abc");
static_cstring<10> b("abd");
BOOST_TEST(a < b);
BOOST_TEST(a <= b);
BOOST_TEST(a <= a);
BOOST_TEST(b > a);
BOOST_TEST(b >= a);
BOOST_TEST(b >= b);
}
// Comparison with CharT const*.
{
static_cstring<10> s("hello");
BOOST_TEST(s == "hello");
BOOST_TEST("hello" == s);
BOOST_TEST(s != "world");
BOOST_TEST("world" != s);
}
// compare().
{
static_cstring<10> s("abc");
BOOST_TEST(s.compare("abc") == 0);
BOOST_TEST(s.compare("abd") < 0);
BOOST_TEST(s.compare("abb") > 0);
}
}
static
void
testCStringConversion()
{
// operator string_view().
{
static_cstring<10> s("hello");
std::string_view sv = s;
BOOST_TEST(sv == "hello");
BOOST_TEST(sv.size() == 5);
}
// str().
{
static_cstring<10> s("hello");
std::string str = s.str();
BOOST_TEST(str == "hello");
BOOST_TEST(str.size() == 5);
}
}
static
void
testCStringStream()
{
static_cstring<10> s("hello");
std::ostringstream oss;
oss << s;
BOOST_TEST(oss.str() == "hello");
}
static
void
testCStringSwap()
{
static_cstring<10> a("hello");
static_cstring<10> b("world");
a.swap(b);
BOOST_TEST(a == "world");
BOOST_TEST(b == "hello");
}
static
void
testCStringConstexpr()
{
// constexpr construction and operations.
constexpr static_cstring<10> ce("test");
static_assert(ce.size() == 4, "");
static_assert(ce[0] == 't', "");
static_assert(ce[1] == 'e', "");
static_assert(ce[2] == 's', "");
static_assert(ce[3] == 't', "");
static_assert(!ce.empty(), "");
static_assert(ce.max_size() == 10, "");
static_assert(ce.capacity() == 10, "");
constexpr static_cstring<5> ce_empty;
static_assert(ce_empty.empty(), "");
static_assert(ce_empty.size() == 0, "");
}
// Helper struct to test NTTP.
template<basic_static_cstring S>
struct NTTPHelper
{
static constexpr std::size_t size() noexcept
{
return S.size();
}
static constexpr const char* c_str() noexcept
{
return S.c_str();
}
};
static
void
testCStringNTTP()
{
// Test non-type template parameter usage (C++20).
// Test size deduction from string literals.
BOOST_TEST(NTTPHelper<"hello">::size() == 5);
BOOST_TEST(NTTPHelper<"">::size() == 0);
BOOST_TEST(NTTPHelper<"test">::size() == 4);
// Test that different strings create different types
constexpr bool same_type = std::is_same_v<
NTTPHelper<"hello">,
NTTPHelper<"hello">>;
BOOST_TEST(same_type);
constexpr bool different_type = !std::is_same_v<
NTTPHelper<"hello">,
NTTPHelper<"world">>;
BOOST_TEST(different_type);
// Test constexpr access.
constexpr std::size_t len = NTTPHelper<"constexpr">::size();
static_assert(len == 9, "");
}
static
void
testCStringPODUsage()
{
// Test usage in POD types (the original motivation).
struct UserRecord
{
int id;
static_cstring<63> name;
unsigned int flags;
};
static_assert(sizeof(static_cstring<63>) == 64, "");
static_assert(std::is_trivially_copyable<UserRecord>::value, "");
UserRecord user{};
user.id = 42;
user.name = "Alice";
user.flags = 0xFF;
BOOST_TEST(user.id == 42);
BOOST_TEST(user.name.size() == 5);
BOOST_TEST(user.name == "Alice");
BOOST_TEST(user.flags == 0xFF);
// Copy the struct.
UserRecord copy = user;
BOOST_TEST(copy.name == "Alice");
// memcpy() should work.
UserRecord memcpy_dest;
std::memcpy(&memcpy_dest, &user, sizeof(UserRecord));
BOOST_TEST(memcpy_dest.name == "Alice");
}
static
void
testCStringLargeCapacity()
{
// Test strings with N > UCHAR_MAX (no remaining-capacity trick).
{
static_cstring<300> s;
BOOST_TEST(s.empty());
BOOST_TEST(s.size() == 0);
s = "This is a test string";
BOOST_TEST(s.size() == 21);
BOOST_TEST(s == "This is a test string");
s.clear();
BOOST_TEST(s.empty());
}
// Large string operations.
{
static_cstring<500> s;
s.assign(400, 'x');
BOOST_TEST(s.size() == 400);
BOOST_TEST(s[0] == 'x');
BOOST_TEST(s[399] == 'x');
BOOST_TEST(*s.end() == 0);
}
}
static
void
testCStringWideChar()
{
// Test with wchar_t.
{
static_wcstring<10> ws;
BOOST_TEST(ws.empty());
BOOST_TEST(ws.size() == 0);
}
{
static_wcstring<10> ws(L"hello");
BOOST_TEST(ws.size() == 5);
BOOST_TEST(ws[0] == L'h');
BOOST_TEST(ws[4] == L'o');
BOOST_TEST(*ws.end() == 0);
}
}
int
runTests()
{
testCStringSizeGuarantee();
testCStringRemainingCapacityTrick();
testCStringTypeTraits();
testCStringConstruct();
testCStringAssignment();
testCStringElements();
testCStringIterators();
testCStringCapacity();
testCStringClear();
testCStringPushPop();
testCStringAppend();
testCStringComparison();
testCStringConversion();
testCStringStream();
testCStringSwap();
testCStringConstexpr();
testCStringNTTP();
testCStringPODUsage();
testCStringLargeCapacity();
testCStringWideChar();
return report_errors();
}
}
}
int
main()
{
return boost::static_strings::runTests();
}