Add 'xo-flatstring/' from commit 'ebbe45e5e7'

git-subtree-dir: xo-flatstring
git-subtree-mainline: 1eb4114722
git-subtree-split: ebbe45e5e7
This commit is contained in:
Roland Conybeare 2025-05-10 19:15:08 -05:00
commit 852dbe66dd
30 changed files with 2464 additions and 0 deletions

View file

@ -0,0 +1,151 @@
name: build xo-flatstring + dependencies
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- name: checkout source
uses: actions/checkout@v3
- name: Install dependencies
run: |
# install catch2, doxygen. see
# [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
echo "::group::install catch2"
sudo apt-get install -y catch2
echo "::endgroup"
echo "::group::install doxygen"
sudo apt-get install -y doxygen
echo "::endgroup"
echo "::group::install sphinx"
sudo apt-get install -y python3-sphinx
echo "::endgroup"
echo "::group::install sphinx readthedocs theme"
sudo apt-get install -y python3-sphinx-rtd-theme
echo "::endgroup"
#echo "::group::install pybind11"
#sudo apt-get install -y pybind11-dev
#echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-cmake
uses: actions/checkout@v3
with:
repository: Rconybea/xo-cmake
path: repo/xo-cmake
- name: build xo-cmake
run: |
XONAME=xo-cmake
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-indentlog
uses: actions/checkout@v3
with:
repository: Rconybea/indentlog
path: repo/xo-indentlog
- name: build xo-indentlog
run: |
XONAME=xo-indentlog
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: build self (xo-flatstring)
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: |
XONAME=xo-flatstring
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::run unit tests ${XONAME}"
cmake --build ${BUILDDIR} -- test
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
(cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}})

View file

@ -0,0 +1,145 @@
name: XO flatstring builder
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
container:
# custom docker image. see github.com:rconybea/docker-xo-builder for definition
image: ghcr.io/rconybea/docker-xo-builder:v1
steps:
- name: xo-cmake
run: |
# treat github.com as known host to prevent shtoopid SSL errors
mkdir -p ~/.ssh
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
cat ~/.ssh/known_hosts
git config --global http.sslVerify false
XO_NAME=xo-cmake
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/xo-cmake.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-indentlog
run: |
XO_NAME=xo-indentlog
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/indentlog.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-flatstring
run: |
XO_NAME=xo-flatstring
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/${XO_NAME}.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::test ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -- test
echo "::endgroup"
echo "::group::build ${XO_NAME} docs"
cmake --build ${XO_BUILDDIR} -- doxygen
# note: sphinx+breathe not working (cause unknown) from docker-xo-builder, asof 25apr2024.
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"

6
xo-flatstring/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# clangd working space (see emacs+lsp)
.cache
# typical cmake build directory (source-tree-nephew)
.build*
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
compile_commands.json

View file

@ -0,0 +1,52 @@
# xo-flatstring/CMakeLists.txt
cmake_minimum_required(VERSION 3.25)
project(xo_flatstring VERSION 1.0)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options2()
# ----------------------------------------------------------------
# cmake -DCMAKE_BUILD_TYPE=coverage
xo_toplevel_coverage_config2()
# ----------------------------------------------------------------
# cmake -DCMAKE_BUILD_TYPE=debug
xo_toplevel_debug_config2()
# ----------------------------------------------------------------
# c++ settings
# one-time project-specific c++ flags. usually empty
set(PROJECT_CXX_FLAGS "")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
add_definitions(${PROJECT_CXX_FLAGS})
# ----------------------------------------------------------------
add_subdirectory(example)
add_subdirectory(utest)
# ----------------------------------------------------------------
set(SELF_LIB xo_flatstring)
xo_add_headeronly_library(${SELF_LIB})
xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets)
# provide find_package() support for projects using this library
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# ----------------------------------------------------------------
# docs targets depend on all the other library/utest targets
#
add_subdirectory(docs)
# ----------------------------------------------------------------
# dependencies
#xo_headeronly_dependency(${SELF_LIB} randomgen)
# etc..
# end CMakeLists.txt

29
xo-flatstring/LICENSE Normal file
View file

@ -0,0 +1,29 @@
Copyright (c) 2024 Roland Conybeare <git3ub@nym.hush.com>, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of
external contributions to this project including patches, pull requests, etc.

86
xo-flatstring/README.md Normal file
View file

@ -0,0 +1,86 @@
# flatstring library
Fixed-length no-allocation string implementation.
Features:
- char array representation with maximum size set at compile time.
- compile time construction from char array and string concatenation
- pointer-free implementation, instances can be used as template arguments
- To the extent practical, provides the same api as `std::string`: includes iterators,
access methods, assignment, conversion operators.
Limitations:
- requires c++20
- not resizable.
- does not support wide characters.
- (asof April 2024) missing features: `insert`, `erase`, `push_back`, `append`, `replace`,
`find`, `compare`, `starts_with`, `ends_with`, `contains`, `substr`.
## Documentation
- xo-flatstring documentation here: [documentation](https://rconybea.github.io/web/xo-flatstring/html/index.html)
- unit test coverage here: [coverage](https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html)
## Getting started
### Install dependencies
- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros
- [github/Rconybea/xo-indentlog](https://github.com/Rconybea/indentlog) logging (used by unit tests)
### Clone xo-flatstring
```
$ cd ~/proj # for example
$ git clone https://github.com/rconybea/xo-flatstring
```
### build + install
```
$ cd xo-flatstring
$ mkdir .build
$ PREFIX=/usr/local # for example
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -B .build
$ cmake --build .build
$ cmake --install .build
```
### build documentation
```
$ cd xo-flatstring
$ cmake --build .build -- docs
```
Must invoke `docs` target explicitly to prepare documentation
When complete, point local browser to `xo-flatstring/.build/docs/sphinx/index.html`
### build with test coverage
```
$ cd xo-flatstring
$ mkdir .build-ccov
$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCMAKE_BUILD_TYPE=coverage -B .build-ccov
$ cmake --build .build-ccov
```
run coverage-enabled unit tests
```
$ cmake --build .build-ccov -- test
```
generate html+text coverage report
```
$ cmake --build .build-ccov -- ccov
```
For html test coverage browse to `.build-ccov/ccov/html/index.html`
install documentation only, along with test-coverage report
```
$ cmake --install .build-ccov --component Documentation
```
### LSP support
```
$ cd xo-flatstring
$ ln -s .build/compile_commands.json
```

View file

@ -0,0 +1,35 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
message(FATAL "could not find xo-cmake-config executable")
endif()
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo_cxx)
xo_cxx_bootstrap_message()

View file

@ -0,0 +1,17 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# note: changes to find_dependency() calls here
# must coordinate with xo_dependency() calls
# in xo-reactor/src/reactor/CMakeLists.txt
#
#find_dependency(reflect)
#find_dependency(subsys)
#find_dependency(Eigen3)
#find_dependency(webutil)
#find_dependency(printjson)
#find_dependency(callback)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

View file

@ -0,0 +1,5 @@
# xo-flatstring/docs/CMakeLists.txt
xo_doxygen_collect_deps()
xo_docdir_doxygen_config()
xo_docdir_sphinx_config(index.rst install.rst lessons.rst flatstring-reference.rst flatstring-class.rst)

1
xo-flatstring/docs/_static/README vendored Normal file
View file

@ -0,0 +1 @@
add any static {.html, .js, ..} files for sphinx to pickup here

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

77
xo-flatstring/docs/_static/img/icon.svg vendored Normal file
View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="108.32474mm"
height="108.39381mm"
viewBox="0 0 108.32474 108.39382"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1"
inkscape:cx="157"
inkscape:cy="322.5"
inkscape:window-width="1607"
inkscape:window-height="1085"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"
fit-margin-top="9.8"
fit-margin-left="8"
fit-margin-right="10"
fit-margin-bottom="9.8" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-47.453609,-27.112369)">
<circle
style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.264583"
id="path31"
cx="68.211342"
cy="87.603088"
r="12.757732" />
<circle
style="fill:#ffcf00;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path31-3"
cx="122.47422"
cy="49.670101"
r="12.757732" />
<circle
style="fill:#6b6bff;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path31-3-6"
cx="133.02061"
cy="112.94845"
r="12.757732" />
<path
style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 73.144329,75.525772 C 85.051544,48.309278 109.54639,49.670102 109.54639,49.670102"
id="path589" />
<path
style="fill:none;stroke:#000000;stroke-width:2.44548;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 76.544292,96.598799 C 95.429921,116.11457 120.4897,113.37017 120.4897,113.37017"
id="path593" />
<path
style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 132,58.175257 c 17.01031,17.350515 7.14433,43.886603 7.14433,43.886603"
id="path788" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="108.32474mm"
height="108.39381mm"
viewBox="0 0 108.32474 108.39382"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="xo-icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1"
inkscape:cx="157"
inkscape:cy="322.5"
inkscape:window-width="1607"
inkscape:window-height="1085"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"
fit-margin-top="9.8"
fit-margin-left="8"
fit-margin-right="10"
fit-margin-bottom="9.8" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-47.453609,-27.112369)">
<circle
style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.264583"
id="path31"
cx="68.211342"
cy="87.603088"
r="12.757732" />
<circle
style="fill:#ffcf00;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path31-3"
cx="122.47422"
cy="49.670101"
r="12.757732" />
<circle
style="fill:#6b6bff;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path31-3-6"
cx="133.02061"
cy="112.94845"
r="12.757732" />
<path
style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 73.144329,75.525772 C 85.051544,48.309278 109.54639,49.670102 109.54639,49.670102"
id="path589" />
<path
style="fill:none;stroke:#000000;stroke-width:2.44548;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 76.544292,96.598799 C 95.429921,116.11457 120.4897,113.37017 120.4897,113.37017"
id="path593" />
<path
style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 132,58.175257 c 17.01031,17.350515 7.14433,43.886603 7.14433,43.886603"
id="path788" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,36 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'xo flatstring documentation'
copyright = '2024, Roland Conybeare'
author = 'Roland Conybeare'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
#extensions = []
extensions = [ "breathe",
"sphinx.ext.autodoc" # generate info from docstrings
]
# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in
# match project name in Doxyfile.in
breathe_default_project = "xodoxxml"
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
pygments_style = 'sphinx'
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
#html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
html_favicon = '_static/img/favicon.ico'

View file

@ -0,0 +1,65 @@
.. _flatstring-class:
Flatstring
==========
.. code-block:: cpp
#include <xo/flatstring/flatstring.hpp>
A ``flatstring`` is a thin wrapper around a character array.
.. doxygenclass:: xo::flatstring
Instance Variables
------------------
.. doxygengroup:: flatstring-instance-variables
:content-only:
Types
-----
.. doxygengroup:: flatstring-types
Constants
---------
.. doxygengroup:: flatstring-constants
:content-only:
Constructors
------------
.. doxygengroup:: flatstring-ctor
:content-only:
Properties
----------
.. doxygengroup:: flatstring-properties
:content-only:
Access Methods
--------------
.. doxygengroup:: flatstring-access
:content-only:
Iterators
---------
.. doxygengroup:: flatstring-iterators
:content-only:
Assignment
----------
.. doxygengroup:: flatstring-assign
:content-only:
Conversion
----------
.. doxygengroup:: flatstring-conversion-operators
:content-only:

View file

@ -0,0 +1,16 @@
.. _flatstring_functions:
.. toctree::
:maxdepth: 2
Flatstring Functions
====================
.. code-block:: cpp
#include <xo/flatstring/flatstring.hpp>
.. doxygenfunction:: xo::flatstring_concat
.. doxygengroup:: flatstring-3way-compare
:content-only:

View file

@ -0,0 +1,11 @@
.. _flatstring-reference:
Flatstring Reference
====================
.. toctree::
:maxdepth: 2
:caption: Flatstring Reference
flatstring-class
flatstring-functions

View file

@ -0,0 +1,38 @@
xo-flatstring documentation master file
xo-flatstring documentation
===========================
xo-flatstring is a lightweight header-only library that provides a constexpr
fixed-size no-allocation string implementation.
Why ``flatstring``?
1. ``flatstring`` instances can be used as template arguments. [1]_
2. ``flatstring`` operations (construction, concatenation, ...) are ``constexpr``, so can be done at compile time. [2]_
3. a ``flatstring`` expression can occupy both compile-time and runtime roles. [3]_
.. [1] A fixed-size char array *can* be used as a template
argument, but char* pointers cannot. Automatic conversion of char arrays to pointers in various contexts
makes them difficult to work with in c++ templates.
.. [2] Although allocation is permitted in constexpr code, it's subject to several restrictions.
it's not yet possible (as of c++23) to use ``std::string`` at compile time.
.. [3] contrast with a solution relying on template arguments, which must then be compile-time-only.
.. toctree::
:maxdepth: 2
:caption: xo-flatstring contents:
install
lessons
flatstring-reference
Indices and Tables
------------------
* :ref:`genindex`
* :ref:`search`

View file

@ -0,0 +1,86 @@
.. _install:
.. toctree
:maxdepth: 2
Source
======
Source code lives on github `here`_
.. _here: https://github.com/rconybea/xo-flatstring
To clone from git:
.. code-block:: bash
git clone https://github.com/rconybea/xo-flatstring
Implementation relies on c++20 features (expanded use of constexpr; class-instances as template arguments).
Tested with gcc 13.2
Install
=======
Since xo-flatstring is header-only, can incorporate into another project just by copying the include directories
to somewhere convenient.
Copy includes
-------------
.. code-block:: bash
# For example..
cd myproject
mkdir -p ext/xo-flatstring
rsync -a -v path/to/xo-flatstring/include/ ext/xo-flatstring/
Include as git submodule
------------------------
.. code-block:: bash
cd myproject
git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring
git submodule update --init
This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``.
You would then add ``myproject/ext/xo-flatstring/include`` to your compiler's include path,
and from c++ do something like
.. code-block:: c++
#include <xo/flatstring/flatstring.hpp>
in c++ source files that rely on xo-flatstring
Supported compilers
-------------------
* developed with gcc 13.2.0; github CI using gcc 11.4.0 (asof April 2024)
Building from source
--------------------
Although the xo-flatstring library is header-only, unit tests have some dependencies.
Example instructions (github CI) for build starting from stock ubuntu are in `ubuntu-main.yml`_
.. _ubuntu-main.yml: https://github.com/Rconybea/xo-flatstring/blob/main/.github/workflows/ubuntu-main.yml
Unit test dependencies:
* `catch2`_ header-only unit-test framework
* `xo-cmake`_ cmake macros
* `xo-indentlog`_ logging with call-structure indenting
.. _catch2: https://github.com/catchorg/Catch2
.. _xo-cmake: https://github.com/rconybea/xo-cmake
.. _xo-indentlog: https://github.com/rconybea/indentlog
To build documentation, will also need:
* `doxygen`
* `graphviz`
* `sphinx`
* `breathe`
* `sphinx_rtd_theme`

View file

@ -0,0 +1,98 @@
.. _lessons:
.. toctree
:maxdepth: 2
Lessons
=======
This is a rogue's gallery of experiments, typically unsuccessful.
One hurdle we've created for ourselves, is we need both gcc and clang to agree
that an expression can be computed at compile-time;
otherwise will get false alarms in our IDE (raised by LSP running in the background, which relies on clang).
Must fully initialize memory
----------------------------
Struggled for a while with the implementation of :ref:xo::flatstring_concat
.. code-block:: cpp
template <std::size_t N>
flatstring::flatstring<N>() {
if (N > 0)
value_[0] = '\0';
}
This implementation satisfies gcc, but not clang: in the following snippet, clang doesn't recognize ``tmp`` as constexpr:
.. code-block:: cpp
constexpr n = ...;
flatstring<n> tmp;
static_assert(tmp.size() == ...); // tmp not constexpr!
Correction is to prove to clang that every memory address owned by an empty ``flatstring`` is initialized:
.. code-block:: cpp
template <std::size_t N>
flatstring::flatstring<N>() {
std::fill_n(value_, N, '\0');
}
Still need equality comparison alongside spaceship operator
-----------------------------------------------------------
Had the impression that spaceship operator for :ref:xo::flatstring would be sufficient
to get all six comparison operators:
.. code-block:: cpp
template <std::size_t N1,
std::size_t N2>
constexpr auto
operator<=>(const flatstring<N1> & s1,
const flatstring<N2> & s2) noexcept
{
return (std::string_view(s1) <=> std::string_view(s2));
}
We observe this is not the case, at least with gcc 13.1; need to separately define :ref:xo::operator==
.. code-block:: cpp
template <std::size_t N1,
std::size_t N2>
constexpr bool
operator==(const flatstring<N1> & s1,
const flatstring<N2> & s2) noexcept
{
return ((s1 <=> s2) == std::strong_ordering::equal);
}
Constexpr strict about pointer arithmetic
-----------------------------------------
Initially attempted to implement :ref:xo::flatstring reverse iterators using char pointers.
Notice there's an assymetry between reverse iterators and forward iterators.
We can (and do) implement forward iterators using char pointers.
The natural value of ``flatstring::end()`` is a char pointer referring to just past the end of
the string, i.e. to its null terminator. From the compiler's perspective, this is an ordinary
char pointer, just like other iterator values.
For reverse iterators this isn't the case. The natural value for ``flatstring::rend()`` might
seem to be a char pointer referring to just before the first character in the string.
However this is no longer a valid pointer address -- dereferencing would be undefined behavior.
In particular, with this implementation, gcc demotes ``flatstring::rend()`` to non-constexpr
Workaround is to implement a shim iterator class, where representation is pointer to the
character just after the one the iterator position; iterator's ``operator*`` adjusts pointer before
dereferencing.
This works because gcc can observe that we never dereference a reverse iterator with pointer value
at the beginning of a flatstring.

View file

@ -0,0 +1 @@
add_subdirectory(ex1)

View file

@ -0,0 +1,15 @@
# xo-flatstring/example/ex1/CMakeLists.txt
set(SELF_EXE xo_flatstring_ex1)
set(SELF_SRCS ex1.cpp)
add_executable(${SELF_EXE} ${SELF_SRCS})
xo_include_options2(${SELF_EXE})
# ----------------------------------------------------------------
# dependencies..
xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring)
#xo_dependency(${SELF_EXE} reflect)
# end CMakeLists.txt

View file

@ -0,0 +1,171 @@
/* @file ex1.cpp */
#include "xo/flatstring/flatstring.hpp"
//#include "xo/stringliteral/stringliteral_iostream.hpp"
//#include "xo/flatstring/experiment.hpp"
//#include "xo/stringliteral/string_view_concat.hpp"
#include <iostream>
int
main() {
using namespace std;
using xo::flatstring;
#ifdef WAITAMO
using xo::stringliteral_compare;
#endif
#ifdef NOT_USING
static_assert(foo1().x_ == 1);
static_assert(foo1().y_ == 2);
constexpr foo1 s1;
static_assert(s1.x_ == 1);
static_assert(s1.y_ == 2);
constexpr foo2 s2;
static_assert(s2.v_[0] == 'a');
static_assert(s2.v_[1] == 'b');
constexpr foo3<2> s3;
static_assert(s3.v_[0] == 'a');
static_assert(s3.v_[1] == 'b');
constexpr foo4<6> s4("hello");
constexpr foo5 s5("hello");
static_assert(s5.v_[0] == 'h');
static_assert(s5.v_[5] == '\0');
constexpr foo6 s6("hello", ", world!");
static_assert(s6.v_[0] == 'h');
static_assert(s6.size() == 13);
cerr << "s6=" << s6.c_str() << endl;
/* z gives allocation size. string size is z-1 */
constexpr std::size_t z = concat_size("hello", ", world!", " What's up?");
static_assert(z == 25);
constexpr foo7<10> s7("Hello", ", world!", " What's up?");
constexpr stringlit<10> s8("0123", "45678");
static_assert(s8.size() == 9);
constexpr std::size_t z8 = stringlit_capacity(s8);
static_assert(sizeof("0123") == 5);
static_assert(sizeof("45") == 3);
static_assert(sizeof("78") == 3);
static_assert(literal_strlen("0123") == 4);
static_assert(z8 == 10);
#endif
#ifdef NOT_USING
static_assert(count_size("0123") == 5);
static_assert(count_size("0123", "45") == 7);
static_assert(count_size("0123", "45", "67", "8") == 10);
constexpr auto z9 = count_size("0123", "45", "78");
static_assert(z9 == 9);
constexpr auto z10 = foofn("0123");
static_assert(z10 == 5);
#endif
#ifdef NOT_USING
//constexpr auto z11 = foofn2("0123");
//static_assert(z9 > 22);
constexpr auto s9 = stringlit_make("0123", "456", "78");
//constexpr auto s9 = stringlit_makepalooza("0123", "45678");
static_assert(s9.size() == 9);
constexpr auto s10 = stringlit_make("0", "123", "456", "78");
static_assert(s10.size() == 9);
cerr << s10.c_str() << endl;
#endif
#ifdef NOT_SUCCESSFUL
constexpr auto s11 = stringlit_make("0", "1", "23", "456", "78");
#endif
#ifdef NOT_USING
constexpr std::size_t z9 = stringlit_capacity(s9, s10);
static_assert(z9 == 19);
constexpr auto s12 = stringlit_cat(s9, s10);
static_assert(s12.size() == 18);
cerr << s12.c_str() << endl;
constexpr auto s13 = stringlit_cat(s9, s10, s12);
static_assert(s13.size() == 36);
cerr << s13.c_str() << endl;
#endif
#ifdef NOT_USING
static_assert(stringliteral_compare(s1, s1) == 0);
cerr << s1 << endl;
#endif
constexpr flatstring s14 = flatstring_concat(flatstring("foo"), flatstring("bar"));
static_assert(s14.fixed_capacity == 7);
static_assert(sizeof(s14) == 7);
constexpr flatstring s15 = flatstring_concat(flatstring("hello"),
flatstring(", "),
flatstring("world"));
static_assert(s15.fixed_capacity == 13);
static_assert(sizeof(s15) == 13);
constexpr auto s16 = xo::flatstring_concat(flatstring("foo"), flatstring("bar"));
static_assert(s16.fixed_capacity == 7);
constexpr auto cmp = flatstring_compare(s14, s14);
static_assert(cmp == 0);
#ifdef WAITAMO
constexpr stringliteral s2 = stringliteral_stringlit_make(stringliteral("hello"),
stringliteral(", world"));
#endif
#ifdef NOT_USING
static constexpr string_view hello("hello");
static constexpr string_view world(" world");
static constexpr auto s3 = stringlit_make_v<hello, world>;
static constexpr string_view hello_world("hello world");
static_assert(s3 == hello_world);
cerr << hello_world << endl;
#endif
}
/* end ex1.cpp */

View file

@ -0,0 +1,641 @@
/** @file flatstring.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include <string_view>
#include <sstream>
#include <algorithm>
#include <memory>
namespace xo {
/** @class flatstring
* @brief class to represent a string with a fixed amount of storage space.
*
* - Flatstring memory layout is a fixed-size, null-terminated char array.
* - With a few exceptions, flatstring methods are noexcept.
* @c flatstring<N>::at() may throw, for consistency with @c std::string::at() behavior
* - Construction and concatenation of flatstrings are constexpr,
* and can be done at compile time.
* We rely on this in related projects (e.g. https://github.com:rconybea/xo-unit)
* - Preserves as much of the c++23 @c std::string api as practicable
*
* @c N includes mandatory null terminator, so we require @c N > 0.
*
* @invariant all flatstring instances are null-terminated.
* @invariant sizeof(flatstring<N>) == N
**/
template <std::size_t N>
struct flatstring {
/** @defgroup flatstring-types template types
* @brief Template types exposed by @c flatstring<N>
**/
///@{
/** @brief character traits for this flatstring **/
using traits_type = std::char_traits<char>;
/** @brief type of each character in this flatstring **/
using value_type = char;
using allocator_type = std::allocator<char>;
using size_type = std::allocator_traits<allocator_type>::size_type;
using difference_type = std::allocator_traits<allocator_type>::difference_type;
/** @brief type of a character reference **/
using reference = value_type &;
/** @brief type of a readonly character reference **/
using const_reference = const value_type &;
using pointer = std::allocator_traits<allocator_type>::pointer;
using const_pointer = std::allocator_traits<allocator_type>::const_pointer;
/** @brief representation for a read/write iterator **/
using iterator = char *;
/** @brief representation for a readonly iterator **/
using const_iterator = const char *;
/** @brief representation for a read/write reverse iterator
*
* constexpr implementation is tricky here, since we can't
* form the address 'just before the beginning of the string' for @p rend()
* without losing constexprness (at least with gcc 13.1)
*
* Instead iterator always refers to the address immediately after its
* real target. This works since @c rbegin() refers to the char just before
* trailing null
**/
struct reverse_iterator {
public:
constexpr reverse_iterator(char * p) : p_{p} {}
constexpr bool _has_pointer() const { return p_ != nullptr; }
constexpr bool operator==(const reverse_iterator & rhs) const noexcept {
return p_ == rhs.p_;
}
constexpr char & operator* () const { return *(p_ - 1); }
constexpr reverse_iterator & operator++ () {
--p_;
return *this;
}
constexpr reverse_iterator operator++ (int) {
reverse_iterator copy = *this;
--p_;
return copy;
}
private:
char * p_;
};
/** @brief representation for a readonly reverse iterator
*
* constexpr implementation is tricky here, since we can't
* form the address 'just before the beginning of the string' for @p rend()
* without losing constexprness (at least with gcc 13.1)
*
* Instead iterator always refers to the address immediately after its
* real target. This works since @c rbegin() refers to the char just before
* trailing null
**/
struct const_reverse_iterator {
public:
constexpr const_reverse_iterator(const char * p) : p_{p} {}
constexpr bool _has_pointer() const { return p_ != nullptr; }
constexpr bool operator==(const const_reverse_iterator & rhs) const noexcept {
return p_ == rhs.p_;
}
constexpr const char & operator* () const { return *(p_ - 1); }
constexpr const_reverse_iterator & operator++ () {
--p_;
return *this;
}
constexpr const_reverse_iterator operator++ (int) {
const_reverse_iterator copy = *this;
--p_;
return copy;
}
private:
const char * p_;
};
///@}
/** @defgroup flatstring-constants constants **/
///@{
static constexpr const size_type npos = size_type(-1);
/** @brief capacity of this flatstring, including final null terminator.
*
* @note not present in @c std::string api
**/
static constexpr const std::size_t fixed_capacity = N;
///@}
public:
/** @defgroup flatstring-ctor constructors **/
///@{
/** @brief create empty string literal. Will contain N null characters
*
* Example
* @code
* constexpr flatstring<5> s1;
* static_assert(s1.empty());
* @endcode
**/
constexpr flatstring() noexcept {
/* note: clang verifies that we fully initialize memory; otherwise will not recognize
* instance as constexpr
*/
std::fill_n(value_, N, '\0');
}
/** @brief create string literal from a correctly-sized char array
*
* Example
* @code
* constexpr flatstring s1("hello");
* static_assert(s1.size() > 0);
* @endcode
**/
constexpr flatstring(const char (&str)[N]) noexcept {
std::copy_n(str, N, value_);
}
///@}
/** @brief construct from another flatstring **/
template <std::size_t N2>
static constexpr flatstring from_flatstring(const flatstring<N2> & str) noexcept {
flatstring retval;
retval.assign(str);
return retval;
}
/** @brief construct from char array **/
template <std::size_t N2>
static constexpr flatstring from_chars(const char (&str)[N2]) noexcept {
flatstring retval;
retval.assign(str);
return retval;
}
/** @brief construct from integer **/
template <typename Int>
requires std::is_integral_v<Int>
static constexpr flatstring from_int(Int x) {
/* 32-bit int ~ 4e9
* 64-bit int ~ 16e18
* 128-bit int - 256e36 ~ 2.6e38
*/
constexpr size_t buf_z = 64;
bool negative_flag = (x < 0);
std::size_t i = buf_z;
char buf[buf_z];
std::fill_n(buf, N, '\0');
if (negative_flag)
x = -x;
buf[--i] = '\0';
if (x == 0)
buf[--i] = '0';
while ((i > 0) && (x != 0)) {
buf[--i] = ('0' + x % 10);
x = x / 10;
}
if ((i > 0) && negative_flag)
buf[--i] = '-';
char retv[N];
std::fill_n(retv, N, '\0');
std::copy_n(buf + i, buf_z - i, retv);
return retv;
}
/** @defgroup flatstring-properties property-methods **/
///@{
/** @brief true if (and only if) string is empty **/
constexpr bool empty() const noexcept { return value_[0] == '\0'; }
/** @brief returns current size of this string **/
constexpr size_type size() const noexcept {
return this->cend() - this->cbegin();
}
/** @brief synonym for @c size() **/
constexpr size_type length() const noexcept { return size(); }
constexpr size_type capacity() const noexcept { return fixed_capacity - 1; }
constexpr size_type max_size() const noexcept { return fixed_capacity - 1; }
/** @brief contents as plain old C-style string. **/
constexpr const char * c_str() const noexcept { return value_; }
///@}
/** @defgroup flatstring-access access methods **/
///@{
/** @brief return char at position @p pos in this string (counting from zero).
*
* Throws @c std::out_of_range exception if @p pos >= @c N
**/
constexpr value_type & at(size_type pos) throw() { return this->at_aux(pos); }
constexpr const value_type & at(size_type pos) const throw() { return const_cast<flatstring *>(this)->at_aux(pos); }
/** @brief return char at position @p pos in this string (counting from zero).
*
* Does not check bounds: undefined behavior if @p pos >= @c N
*
* @pre 0<=pos<=N-1
**/
constexpr value_type & operator[](size_type pos) noexcept { return value_[pos]; }
constexpr const value_type & operator[](size_type pos) const noexcept { return value_[pos]; }
///@}
/** @defgroup flatstring-iterators iterators **/
///@{
constexpr iterator begin() { return &value_[0]; }
constexpr iterator end() { return this->last<iterator>(); }
constexpr const_iterator cbegin() const { return &value_[0]; }
constexpr const_iterator cend() const { return const_cast<flatstring*>(this)->last<iterator>(); }
constexpr const_iterator begin() const { return cbegin(); }
constexpr const_iterator end() const { return cend(); }
constexpr reverse_iterator rbegin() { return reverse_iterator(this->last<iterator>()); }
constexpr reverse_iterator rend() { return reverse_iterator(&value_[0]); }
constexpr const_reverse_iterator crbegin() const { return const_cast<flatstring*>(this)->last<iterator>(); }
constexpr const_reverse_iterator crend() const { return &value_[0]; }
constexpr const_reverse_iterator rbegin() const { return crbegin(); }
constexpr const_reverse_iterator rend() const { return crend(); }
///@}
/** @defgroup flatstring-assign assignment **/
///@{
/** @brief put string into empty state. fills entire char array with nulls **/
constexpr void clear() noexcept { std::fill_n(value_, N, '\0'); }
/** @brief replace contents with min(count,N-1) copies of character ch **/
constexpr flatstring & assign(size_type count, value_type ch) {
std::size_t pos = 0;
for (; pos < std::min(count, N-1); ++pos)
value_[pos] = ch;
for (; pos < N; ++pos)
value_[pos] = '\0';
return *this;
}
/** @brief replace contents with first N-1 characters of @p x **/
constexpr flatstring & assign(const flatstring & x) {
for (std::size_t pos = 0; pos < N-1; ++pos)
value_[pos] = x.value_[pos];
value_[N-1] = '\0';
return *this;
}
/** @brief replace contents with substring [pos,pos+count] of str **/
template <std::size_t N2>
constexpr flatstring & assign(const flatstring<N2> & x,
size_type pos, size_type count = npos) {
std::size_t i = 0;
for (;
i < std::min(std::min(count,
(x.fixed_capacity-1 > pos)
? x.fixed_capacity-1 - pos
: 0ul),
N-1);
++i)
value_[i] = x.value_[pos+i];
for (; i < N; ++i)
value_[i] = '\0';
return *this;
}
/** @brief replace contents with range [cstr, cstr + count) **/
constexpr flatstring & assign(const value_type * cstr, size_type count) {
std::size_t i = 0;
for (; i < std::min(N-1, count); ++i)
value_[i] = cstr[i];
for (; i < N; ++i)
value_[i] = '\0';
return *this;
}
/** @brief replace contents with C-style string cstr **/
constexpr flatstring & assign(const value_type * cstr) {
std::size_t i = 0;
const value_type * p = cstr;
while ((i < N-1) && (*p != '\0')) {
value_[i] = *p;
++i;
++p;
}
for (; i < N; ++i)
value_[i] = '\0';
return *this;
}
/** @brief replace contents with iterator range [first, last) **/
template <typename InputIter>
constexpr flatstring & assign(InputIter first, InputIter last) {
InputIter ix = first;
std::size_t i = 0;
for (; (i < N-1) && (ix != last); ++i) {
value_[i] = *ix;
}
for (; i < N; ++i)
value_[i] = '\0';
return *this;
}
///@}
/** @defgroup flatstring-append append **/
///@{
/** @brief append contents of null-terminated string cstr **/
constexpr flatstring & append(const value_type * cstr) {
std::size_t z = this->size();
std::size_t i = 0;
for (; (z+i < N-1) && (cstr[i] != '\0'); ++i)
value_[z+i] = cstr[i];
for (; z+i < N; ++i)
value_[z+i] = '\0';
return *this;
}
/** @brief append the first count members of cstr[] **/
constexpr flatstring & append(const value_type * cstr, size_type count) {
std::size_t z = this->size();
std::size_t i = 0;
for (; z+i < std::min(N-1, count); ++i)
value_[z+i] = cstr[i];
for (; z+i < N; ++i)
value_[z+i] = '\0';
return *this;
}
/** @brief append substring [pos .. pos + count) of x **/
template <std::size_t N2>
constexpr flatstring & append(const flatstring<N2> & x,
size_type pos, size_type count = npos)
{
std::size_t i_src = 0;
std::size_t i_dest = size();
for (;
i_src < std::min(std::min(count,
(x.fixed_capacity-1 > pos)
? x.fixed_capacity-1 - pos
: 0ul),
N-1);
++i_src, ++i_dest)
value_[i_dest] = x.value_[pos+i_src];
for (; i_dest < N; ++i_dest)
value_[i_dest] = '\0';
return *this;
}
///@}
// insert
// insert_range
// erase
// push_back
// append
// append_range
// operator+=
// replace
// replace_with_range
// copy
// find
// rfind
// find_first_of
// find_first_not_of
// find_last_of
// find_last_not_of
// compare
// starts_with
// end_with
// contains
// substr
/** @defgroup flatstring-conversion-operators conversion operators **/
///@{
/** @brief conversion to @c std::string
*
* Example
* @code
* constexpr flatstring s("bazinga!");
* std::string s_str{s.str()};
* @endcode
**/
std::string str() const { return std::string(value_); }
/** @brief conversion operator to string_view **/
constexpr operator std::string_view() const noexcept { return std::string_view(value_); }
/** @brief conversion operator to C-style string.
*
* Example
* @code
* constexpr flatstring s("obey gravity..");
* strcmp(s, "obey...");
* @endcode
**/
constexpr operator const char * () const noexcept { return value_; }
///@}
private:
constexpr value_type & at_aux(size_type pos) {
if (pos >= N) {
#ifdef NOT_USING
/* note: can't build stringstream at compile time */
std::stringstream ss;
ss << "flatstring<" << N << ">::at: expected pos=[" << pos << "] in interval [0," << N << ")" << std::endl;
#endif
throw std::out_of_range("at_aux: range error");
}
return (*this)[pos];
}
template <typename Iterator>
constexpr Iterator last() noexcept {
Iterator p = &value_[N-1];
/* search backward for first padding '\0' */
while ((p > &value_[0]) && (*(p-1) == '\0'))
--p;
return p;
}
public:
/** @defgroup flatstring-instance-variables instance variables **/
///@{
/** @brief characters comprising this literal string **/
char value_[N];
///@}
};
/** @brief sentinel type, for forbidden flatstring with no space for a null terminator **/
template <>
struct flatstring<0> { flatstring() = delete; };
// non-member functions
// erase
// erase_if
// operator<<
// operator>>
// getline
// stoi
// stol
// stoll
// stoul
// stoull
// stof
// stod
// stold
#ifdef NOT_USING
/** @brief all_same_v<T1, .., Tn> is true iff types T1 = .. = Tn
**/
template < typename First, typename... Rest >
constexpr auto
all_same_v = std::conjunction_v< std::is_same<First, Rest>... >;
#endif
/** @brief Concatenate flatstrings, possibly mixed with C-style char arrays
*
* Example:
* @code
* constexpr auto s = flatstring_concat(flatstring("hello"),
* ", ",
* flatstring("world"));
* static_assert(s.capacity == 13);
* @endcode
*
**/
template < typename... Ts>
constexpr auto
flatstring_concat(Ts && ... args) noexcept
{
#ifdef NOT_USING
static_assert(all_same_v<std::decay_t<Ts>...>,
"string must share the same char type");
using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >;
#endif
using value_type = char;
/** n1: total number of bytes used by arguments **/
constexpr std::size_t n1 = (sizeof(Ts) + ...);
/** z1: each string arg has a null terminator included in its size,
* z1 is the number of arguments in parameter pack Ts,
* which equals the number of null terminators used
**/
constexpr std::size_t z1 = sizeof...(Ts);
/** n: number of chars in concatenated string. +1 for final null **/
constexpr std::size_t n
= (n1 / sizeof(value_type)) - z1 + 1;
flatstring<n> result;
std::size_t pos = 0;
auto detail_concat = [ &pos, &result ](auto && arg) {
/* tradeoff here:
* 1. flatstring::size() is constexpr, so we can concat strings with size() < capacity().
* (note flatstring::from_int() likely creates such strings)
* 2. ..but no size() method on char arrays.
* 3. std::size() not suitable: size of char array includes null terminator,
* while flatstring.size() excludes it, and flatstring behavior is consistent with
* std::string.size()
* Consequence of using arg.size() here; have to wrap char arrays with
* flatstring() to use them with flatstring_concat()
*/
auto count = arg.size();
//constexpr auto count = (sizeof(arg) - sizeof(value_type)) / sizeof(value_type);
std::copy_n(/*arg.c_str()*/ static_cast<const char *>(arg), count, result.value_ + pos);
pos += count;
};
(detail_concat(args), ...);
return result;
}
/** @brief compare two flatstrings lexicographically.
*
* Example:
* @code
* constexpr auto cmp = flatstring_compare(flatstring("foo"), flatstring("bar"));
* static_assert(cmp > 0);
* @endcode
**/
template <std::size_t N1,
std::size_t N2>
constexpr auto
flatstring_compare(const flatstring<N1> & s1,
const flatstring<N2> & s2) noexcept
{
return (std::string_view(s1.value_) <=> std::string_view(s2.value_));
}
/** @defgroup flatstring-3way-compare 3way-compare **/
///@{
/** @brief 3-way compare for two flatstrings
*
* Example
* @code
* constexpr auto cmp = (flatstring("foo") <=> flatstring("bar"));
* static_assert(cmp != 0);
* @endcode
**/
template <std::size_t N1,
std::size_t N2>
constexpr auto
operator<=>(const flatstring<N1> & s1,
const flatstring<N2> & s2) noexcept
{
return (std::string_view(s1) <=> std::string_view(s2));
}
/** @brief equality comparison for two flatstrings.
*
* Example
* @code
* constexpr bool cmp = (flatstring("foo") == flatstring("foo"));
* static_assert(cmp == true);
* @endcode
*
* @note spaceship operator alone isn't sufficient to get this defined,
* at least with gcc 13.1
**/
template <std::size_t N1,
std::size_t N2>
constexpr bool
operator==(const flatstring<N1> & s1,
const flatstring<N2> & s2) noexcept
{
return ((s1 <=> s2) == std::strong_ordering::equal);
}
///@}
} /*namespace xo*/
/** end flatstring.hpp **/

View file

@ -0,0 +1,37 @@
/** @file flatstring_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "flatstring.hpp"
#include <ostream>
//#include <cstdint>
namespace xo {
/** @brief print flatstring on stream os.
*
**/
template <std::size_t N>
void
print_flatstring (std::ostream & os, const flatstring<N> & x) {
os << x.c_str();
}
/** @brief print flatstring x on stream os.
*
* Example
* @code
* cout << flatstring("foo"); // outputs "foo"
* @endcode
**/
template <std::size_t N>
inline std::ostream &
operator<< (std::ostream & os, const flatstring<N> & x) {
print_flatstring(os, x);
return os;
}
} /*namespace xo*/
/** end flatstring_iostream.hpp **/

View file

@ -0,0 +1,31 @@
/** @file int128_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "flatstring.hpp"
#include <ostream>
namespace std {
/* print a 128-bit integer */
inline std::ostream &
operator<< (std::ostream & os, __int128 x) {
os << xo::flatstring<48>::from_int(x);
return os;
}
}
#ifdef NOT_USING
namespace xo {
/* print a 128-bit integer */
inline std::ostream &
operator<< (std::ostream & os, __int128 x) {
os << xo::flatstring<48>::from_int(x);
return os;
}
}
#endif
/** end int128_iostream.hpp **/

View file

@ -0,0 +1,30 @@
#include <string_view>
#include <array>
template <std::string_view const & ... Strings>
struct sv_concat
{
static constexpr auto impl() noexcept {
constexpr std::size_t n = (Strings.size() + ... + 0);
std::array<char, n + 1> arr{};
auto append = [i=0, &arr](const auto & s) mutable {
for (auto c : s)
arr[i++] = c;
};
(append(Strings), ...);
arr[n] = '\0';
return arr;
}
static constexpr auto arr = impl();
static constexpr std::string_view value {
arr.data(),
arr.size() - 1
};
};
template <std::string_view const & ... Strings>
static constexpr auto concat_v = sv_concat<Strings...>::value;

View file

@ -0,0 +1,56 @@
# xo-flatstring/utest/CMakeLists.txt
set(SELF_EXE utest.flatstring)
set(SELF_SRCS
flatstring_utest_main.cpp
flatstring.test.cpp)
xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS})
# ----------------------------------------------------------------
# in coverage build, target to build+install coverage report
if (XO_SUBMODULE_BUILD)
# in submodule build, generate aggregate coverage report
# for all xo libraries
else()
set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html)
set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html)
set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov)
# CMAKE_INSTALL_DOCDIR
# =default=> DATAROOTDIR/doc/PROJECT_NAME
# =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring
set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov)
# 'test' target should always be out-of-date
#
# DEPENDS: reminder - can't put 'test' here, requires 'all' target
#
add_custom_command(
OUTPUT ${CCOV_INDEX_FILE}
DEPENDS ${SELF_EXE}
COMMAND ${CCOV_REPORT_EXE}
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]")
add_custom_target(
ccov
DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE})
# OPTIONAL: quietly skip this step if ccov report not generated
install(
DIRECTORY ${CCOV_OUTPUT_DIR}
FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
DESTINATION ${CCOV_INSTALL_DOCDIR}
COMPONENT Documentation
OPTIONAL)
endif()
# ----------------------------------------------------------------
# deps: logutils, ...
xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring)
xo_dependency(${SELF_EXE} indentlog)
xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2)
# end CMakeLists.txt

View file

@ -0,0 +1,445 @@
/** @file flatstring.utest.cpp **/
#include "xo/flatstring/flatstring.hpp"
#include "xo/flatstring/int128_iostream.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/tag.hpp"
#include "xo/indentlog/print/hex.hpp"
#include <catch2/catch.hpp>
#include <type_traits>
//#include <iostream>
namespace xo {
using namespace std;
namespace ut {
template <typename String>
void
flatstring_iter_tests(const String & str, const char * text) {
size_t n = ::strlen(text);
REQUIRE(str.size() == n);
/* verify range iteration visits contents in order */
{
size_t i = 0;
for (char ch : str) {
INFO(XTAG(i));
CHECK(ch == text[i]);
++i;
}
REQUIRE(i == n);
}
String str_copy;
REQUIRE(str_copy.capacity() == str.capacity());
REQUIRE(str_copy.empty());
/* verify const iteration visits string elements in order */
{
str_copy = str;
REQUIRE(str_copy == str);
size_t i = 0;
for (auto ix = str_copy.cbegin(), end_ix = str_copy.cend(); ix != end_ix; ++ix) {
INFO(XTAG(i));
char ch = *ix;
CHECK(ch == text[i]);
++i;
}
REQUIRE(i == n);
}
/* verify string overwrite through iterator */
{
size_t i = 0;
for (auto ix = str_copy.begin(), end_ix = str_copy.end(); ix != end_ix; ++ix) {
INFO(XTAG(i));
*ix = ('a' + i);
++i;
}
REQUIRE(i == n);
for (i = 0; i < n; ++i) {
CHECK(str_copy[i] == ('a' + i));
}
}
/* verify reverse iteration visits string elements in reverse order */
{
str_copy = str;
REQUIRE(str_copy == str);
size_t i = 0;
for (auto ix = str_copy.rbegin(), end_ix = str_copy.rend(); ix != end_ix; ++ix) {
INFO(XTAG(i));
char ch = *ix;
CHECK(ch == text[n-1-i]);
++i;
}
REQUIRE(i == n);
}
/* verify string overwrite through reverse iterator */
{
str_copy = str;
REQUIRE(str_copy == str);
size_t i = 0;
for (auto ix = str_copy.rbegin(), end_ix = str_copy.rend(); ix != end_ix; ++ix) {
INFO(XTAG(i));
*ix = ('a' + i);
++i;
}
REQUIRE(i == n);
for (i = 0; i< n; ++i) {
CHECK(str_copy[n-1-i] == ('a' + i));
}
}
/* verify const reverse iteration visits string elements in reverse order */
{
str_copy = str;
REQUIRE(str_copy == str);
size_t i = 0;
for (auto ix = str_copy.crbegin(), end_ix = str_copy.crend(); ix != end_ix; ++ix) {
INFO(XTAG(i));
char ch = *ix;
CHECK(ch == text[n-1-i]);
++i;
}
REQUIRE(i == n);
}
}
template <typename String1, typename String2>
void
flatstring_assign_tests(const String1 & str, const char * text,
const String2 & str2, const char * text2) {
INFO(tostr(XTAG(str), XTAG(text), XTAG(text2)));
String1 str_copy;
str_copy.assign(str.c_str());
REQUIRE(str_copy == str);
/* verify assignment from C-style string **/
{
str_copy.assign(text2);
INFO(tostr(XTAG(str_copy), XTAG(text2)));
REQUIRE(::strncmp(str_copy.c_str(), text2,
std::min(::strlen(text2)+1, str_copy.capacity())) == 0);
}
/* verify assignment from prefix of C-style string */
for (size_t prefix = 0, n_prefix = ::strlen(text2); prefix < n_prefix; ++prefix)
{
str_copy.assign(str);
REQUIRE(str_copy == str);
str_copy.assign(text2, prefix);
INFO(tostr(XTAG(prefix), XTAG(str_copy), XTAG(text2)));
if (prefix == 0) {
REQUIRE(str_copy.empty());
} else {
REQUIRE(str_copy.size() == std::min(prefix, str_copy.capacity()));
REQUIRE(::strncmp(str_copy.c_str(), text2,
std::min(prefix, str_copy.capacity())) == 0);
}
}
/* verify assignment from substring */
String2 text2_copy;
text2_copy.assign(text2);
INFO(tostr(XTAG(text2_copy)));
for (size_t i = 0, n = text2_copy.size(); i < n; ++i) {
/* deliberately letting j extend beyond the end of text2_copy */
for (size_t j = i; j < n+10; ++j) {
INFO(tostr(XTAG(n), XTAG(i), XTAG(j)));
str_copy.assign(str);
REQUIRE(str_copy == str);
str_copy.assign(text2_copy, i, j-i);
INFO(tostr(XTAG(str_copy.fixed_capacity), XTAG(str_copy)));
REQUIRE(str_copy.size() == std::min(j-i,
std::min(text2_copy.size()-i,
str_copy.capacity())));
REQUIRE(::strncmp(str_copy.c_str(), text2_copy.c_str() + i,
std::min(j-i, str_copy.capacity())) == 0);
}
}
}
template <typename String1, typename String2>
void
flatstring_concat_tests(const String1 & str, const char * text,
const String2 & str2, const char * text2)
{
flatstring<String1::fixed_capacity + String2::fixed_capacity - 1> concat;
REQUIRE(concat.empty());
/* forcing concat to occur at runtime */
{
concat = flatstring_concat(str, str2);
auto req_str = string(text) + string(text2);
REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0);
}
{
concat = flatstring_concat(str2, str);
auto req_str = string(text2) + string(text);
REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0);
}
#ifdef NOT_USING
{
auto concat4 = flatstring_concat(str,
flatstring(text2),
str,
flatstring(text2));
auto req_str = string(text) + string(text2) + string(text) + string(text2);
REQUIRE(::strcmp(concat4.c_str(), req_str.c_str()) == 0);
}
#endif
{
auto concat4 = flatstring_concat(str, str2, str, str2);
auto req_str = string(text) + string(text2) + string(text) + string(text2);
REQUIRE(::strcmp(concat4.c_str(), req_str.c_str()) == 0);
}
}
template <typename String>
void
flatstring_runtime_tests(const String & str, const char * text) {
INFO(tostr(XTAG(str), XTAG(text)));
REQUIRE(str.fixed_capacity == strlen(text)+1);
REQUIRE(str.capacity() == strlen(text));
REQUIRE(str.size() == strlen(text));
REQUIRE(str.length() == strlen(text));
REQUIRE(strcmp(str.c_str(), text) == 0);
REQUIRE(strcmp(str, text) == 0);
String str2 = str;
{
string str3{str.str()};
REQUIRE(::strcmp(str3.c_str(), str.c_str()) == 0);
}
REQUIRE(string_view(str2) == string_view(str));
{
auto cmp = (str2 <=> str);
REQUIRE(cmp == strong_ordering::equal);
}
{
bool cmp = (str2 == str);
INFO(xtag("cmp", cmp));
REQUIRE(str2 == str);
bool cmp2 = (str2 != str);
REQUIRE(cmp2 != cmp);
}
str2.clear();
REQUIRE(str2.empty());
str2.assign(100, ' ');
REQUIRE(str2.size() == str2.capacity());
/* verify entirely ' ' */
{
size_t i = 0;
for (char ch : str2) {
INFO(XTAG(i));
CHECK(ch == ' ');
++i;
}
REQUIRE(i == str2.size());
}
}
/* using macro here because template argument depends on size of literal C string,
* and we can't use such a string as a template argument.
*
* static_asserts: using these to verify that constexpr methods are being computed
* at compile time.
*
* REQUIRE() calls to do verification that relies on non-constexpr calls such as
* strlen(), strcmp()
*/
# define LITERAL_TEST_BODY(name, name2, text, text2) \
constexpr flatstring name{text}; \
constexpr flatstring name2{text2}; \
static_assert(name[0]==text[0]); \
static_assert(name.at(0)==text[0]); \
static_assert(name.empty() == true || name.empty() == false); \
static_assert(name.capacity() >= 0); \
static_assert(name.begin() != nullptr); \
static_assert(name.end() != nullptr); \
static_assert(name.cbegin() != nullptr); \
static_assert(name.cend() != nullptr); \
static_assert(name.crbegin()._has_pointer()); \
static_assert(name.crend()._has_pointer()); \
/*static_assert(name.rbegin() != nullptr);*/ \
/*static_assert(!name.rend());*/ \
static_assert(name.size() >= 0); \
static_assert(name.c_str() != nullptr); \
static_assert((name <=> name) == 0); \
static_assert(name == name); \
static_assert(name >= name); \
static_assert(name <= name); \
static_assert(!(name != name)); \
static_assert(!(name > name)); \
static_assert(!(name < name)); \
flatstring_runtime_tests(name, text); \
flatstring_iter_tests(name, text); \
flatstring_assign_tests(name, text, name2, text2); \
flatstring_concat_tests(name, text, name2, text2); \
static_assert(string_view(name) == string_view(name)); \
/* end LITERAL_TEST_BODY */
TEST_CASE("flatstring", "[flatstring][compile-time]") {
constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.flatstring"));
//log && log("(A)", xtag("foo", foo));
/* mostly compile-time tests here */
LITERAL_TEST_BODY(s1, t1, "h", "abracadabra!");
LITERAL_TEST_BODY(s2, t2, "he", "bracadabra!");
LITERAL_TEST_BODY(s3, t3, "hel", "racadabra!");
LITERAL_TEST_BODY(s4, t4, "hell", "acadabra!");
LITERAL_TEST_BODY(s5, t5, "hello", "cadabra!");
LITERAL_TEST_BODY(s6, t6, "hello,", "adabra!");
LITERAL_TEST_BODY(s7, t7, "hello, ", "dabra!");
LITERAL_TEST_BODY(s8, t8, "hello, w", "abra!");
LITERAL_TEST_BODY(s9, t9, "hello, wo", "bra!");
LITERAL_TEST_BODY(s10, t10, "hello, wor", "ra!");
LITERAL_TEST_BODY(s11, t11, "hello, worl", "a!");
LITERAL_TEST_BODY(s12, t12, "hello, world", "!");
LITERAL_TEST_BODY(s13, t13, "hello, world!", "");
static_assert(s1 == s1);
static_assert(s1 != s2);
static_assert(s2 != s3);
static_assert(s3 != s4);
static_assert(s4 != s5);
static_assert(s12 != s13);
static_assert(s1 < s2);
static_assert(s2 < s3);
static_assert(s3 < s4);
static_assert(s4 < s5);
static_assert(s12 < s13);
static_assert(s2 > s1);
static_assert(s3 > s2);
static_assert(s4 > s3);
static_assert(s5 > s4);
static_assert(s13 > s12);
/* concat */
static_assert(flatstring_concat(s1,t1) == flatstring("habracadabra!"));
/* clear */
auto s13_copy = s13;
s13_copy.clear();
REQUIRE(s13_copy.empty());
constexpr auto s13_copy2 = s13;
static_assert(s13_copy2.size() == s13.size());
//cerr << "s13=[" << s13 << "] s13_copy2=[" << s13_copy2 << "]" << endl;
//cerr << xtag("s13", hex_view(s13.c_str(), s13.c_str() + s13.capacity(), true)) << endl;
//cerr << xtag("s13_copy2", hex_view(s13_copy2.c_str(), s13_copy2.c_str() + s13_copy2.capacity(), true)) << endl;
REQUIRE(s13_copy2 == s13);
} /*TEST_CASE(flatstring)*/
TEST_CASE("flatstring_int128", "[flatstring]") {
//constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
//scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.flatstring_int128"));
//log && log("(A)", xtag("foo", foo));
__int128_t x = 65536UL*65536UL*65536UL*65536UL*65536UL;
stringstream ss;
ss << x;
} /*TEST_CASE(flatstring_int128)*/
} /*namespace ut*/
} /*namespace xo*/
/** end flatstring.utest.cpp **/

View file

@ -0,0 +1,6 @@
/** @file flatstring_utest_main.cpp **/
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
/** end flatstring_utest_main.cpp **/