xo-flatstring: + README + docs + test coverage
This commit is contained in:
parent
75799f4652
commit
a958217c38
26 changed files with 4161 additions and 156 deletions
|
|
@ -1,8 +1,8 @@
|
|||
# xo-stringliteral/CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
|
||||
project(xo_stringliteral VERSION 1.0)
|
||||
project(xo_flatstring VERSION 1.0)
|
||||
enable_language(CXX)
|
||||
|
||||
# common XO cmake macros (see proj/xo-cmake)
|
||||
|
|
@ -12,17 +12,37 @@ include(cmake/xo-bootstrap-macros.cmake)
|
|||
# unit test setup
|
||||
|
||||
enable_testing()
|
||||
# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON)
|
||||
add_code_coverage()
|
||||
|
||||
# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc.
|
||||
# we're not interested in code coverage for these sources.
|
||||
# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves;
|
||||
# rather, want coverage on the code that the unit tests exercise.
|
||||
# ----------------------------------------------------------------
|
||||
# cmake -DCMAKE_BUILD_TYPE=coverage
|
||||
|
||||
if (NOT DEFINED PROJECT_CXX_FLAGS_COVERAGE)
|
||||
# note: for clang would use -fprofile-instr-generate -fcoverage-mapping here instead and also at link time
|
||||
set(PROJECT_CXX_FLAGS_COVERAGE ${PROJECT_CXX_FLAGS} -ggdb -Og -fprofile-arcs -ftest-coverage
|
||||
CACHE STRING "coverage c++ compiler flags")
|
||||
endif()
|
||||
message("-- PROJECT_CXX_FLAGS_COVERAGE: coverage c++ flags are [${PROJECT_CXX_FLAGS_COVERAGE}]")
|
||||
|
||||
add_compile_options("$<$<CONFIG:COVERAGE>:${PROJECT_CXX_FLAGS_COVERAGE}>")
|
||||
# when -DCMAKE_BUILD_TYPE=coverage, link executables with gcov
|
||||
link_libraries("$<$<CONFIG:COVERAGE>:gcov>")
|
||||
|
||||
find_program(LCOV_EXECUTABLE NAMES lcov)
|
||||
find_program(GENHTML_EXECUTABLE NAMES genhtml)
|
||||
|
||||
# with coverage build:
|
||||
# 1. invoke instrumented executables for which you want coverage:
|
||||
# (cd path/to/build && ctest)
|
||||
# 2. post-process low-level coverage data
|
||||
# (path/to/build/gen-ccov)
|
||||
# 3. point browser to generated html data
|
||||
# file:///path/to/build/ccov/html/index.html
|
||||
#
|
||||
# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target
|
||||
#
|
||||
add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*)
|
||||
configure_file(
|
||||
${PROJECT_SOURCE_DIR}/cmake/gen-ccov.in
|
||||
${PROJECT_BINARY_DIR}/gen-ccov)
|
||||
|
||||
file(CHMOD ${PROJECT_BINARY_DIR}/gen-ccov PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# c++ settings
|
||||
|
|
@ -37,8 +57,8 @@ xo_toplevel_compile_options()
|
|||
# ----------------------------------------------------------------
|
||||
|
||||
add_subdirectory(example)
|
||||
#add_subdirectory(utest)
|
||||
#add_subdirectory(docs)
|
||||
add_subdirectory(utest)
|
||||
add_subdirectory(docs)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# provide find_package() support for projects using this library
|
||||
|
|
|
|||
59
README.md
Normal file
59
README.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# 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`
|
||||
|
||||
Limitations:
|
||||
- requires c++20
|
||||
- not resizable.
|
||||
- does not support wide characters.
|
||||
|
||||
## Getting started
|
||||
|
||||
### 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
|
||||
```
|
||||
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 -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -B .build-ccov
|
||||
$ cmake --build .build-ccov
|
||||
```
|
||||
|
||||
run coverage-enabled unit tests
|
||||
```
|
||||
$ (cd .build-ccov && ctest)
|
||||
```
|
||||
|
||||
generate html+text coverage report
|
||||
```
|
||||
$ .build-ccov/gen-ccov
|
||||
```
|
||||
|
||||
browse to `.build-ccov/ccov/html/index.html`
|
||||
|
||||
### LSP support
|
||||
```
|
||||
$ cd xo-flatstring
|
||||
$ ln -s .build/compile_commands.json
|
||||
```
|
||||
20
cmake/gen-ccov.in
Normal file
20
cmake/gen-ccov.in
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
srcdir=@PROJECT_SOURCE_DIR@
|
||||
builddir=@PROJECT_BINARY_DIR@
|
||||
lcov=@LCOV_EXECUTABLE@
|
||||
genhtml=@GENHTML_EXECUTABLE@
|
||||
|
||||
if [[ $lcov == "LCOV_EXECUTABLE-NOTFOUND" ]]; then
|
||||
echo "gen-ccov: lcov executable not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $genhtml == "GENHTML_EXECUTABLE-NOTFOUND" ]]; then
|
||||
echo "gen-ccov: genhtml executable not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir $builddir/ccov
|
||||
|
||||
$srcdir/cmake/lcov-harness $srcdir $builddir $builddir/ccov/out $lcov $genhtml
|
||||
114
cmake/lcov-harness
Executable file
114
cmake/lcov-harness
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
srcdir=$1
|
||||
builddir=$2
|
||||
outputstem=$3
|
||||
lcov=$4
|
||||
genhtml=$5
|
||||
|
||||
if [[ -z "${srcdir}" ]]; then
|
||||
echo "lcov-harness: expected non-empty srcdir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${builddir} ]]; then
|
||||
echo "lcov-harness: expected non-empty builddir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${outputstem} ]]; then
|
||||
echo "lcov-harness: expected non-empty outputstem"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${lcov} ]]; then
|
||||
echo "lcov-harness: exepcted non-empty lcov"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${genhtml} ]]; then
|
||||
echo "lcov-harness: expected non-empty genhtml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# directory stems for location of {.gcda, gcno} coverage information,
|
||||
#
|
||||
# if we have source tree:
|
||||
#
|
||||
# ${srcdir}
|
||||
# +- foo
|
||||
# | \- foo.cpp
|
||||
# \- bar
|
||||
# \- quux
|
||||
# +- quux.cpp
|
||||
# \- quux_main.cpp
|
||||
#
|
||||
# then we expect build tree:
|
||||
#
|
||||
# ${builddir}
|
||||
# +- foo
|
||||
# | \- CMakeFiles
|
||||
# | \- foo_target.dir
|
||||
# | +- foo.cpp.gcda
|
||||
# | \- foo.cpp.gcno
|
||||
# +- bar
|
||||
# \- quux
|
||||
# \- CMakeFiles
|
||||
# \- target4quux.dir
|
||||
# +- quux.cpp.gcda
|
||||
# +- quux.cpp.gcno
|
||||
# +- quux_main.cpp.gcda
|
||||
# \- quux_main.cpp.gcno
|
||||
#
|
||||
# in which case will have cmd_body:
|
||||
#
|
||||
# ${primarydirs}
|
||||
# ./foo/CMakeFiles/foo_target.dir
|
||||
# ./bar/quux/CMakeFiles/target4quux.dir
|
||||
#
|
||||
# here foo_target, quux_target are whatever build is using for corresponding cmake target names.
|
||||
#
|
||||
# We want to invoke lcov like:
|
||||
#
|
||||
# lcov --capture \
|
||||
# --output ${builddir}/ccov \
|
||||
# --exclude /utest/ \
|
||||
# --base-directory ${srcdir}/foo --directory ${builddir}/foo/CMakeFiles/foo_target.dir \
|
||||
# --base-directory ${srcdir}/bar/quux --directory ${builddir}/bar/quux/CMakeFiles/target4quux.dir
|
||||
#
|
||||
primarydirs=$(cd ${builddir} && find -name '*.gcno' \
|
||||
| xargs --replace=xx dirname xx \
|
||||
| uniq \
|
||||
| sed -e 's:^\./::')
|
||||
|
||||
#echo "primarydirs=${primarydirs}"
|
||||
|
||||
cmd="${lcov} --output ${outputstem}.info --capture --ignore-errors source"
|
||||
|
||||
for bdir in ${primarydirs}; do
|
||||
sdir=$(dirname $(dirname ${bdir}))
|
||||
|
||||
cmd="${cmd} --base-directory ${srcdir}/${sdir} --directory ${builddir}/${bdir}"
|
||||
done
|
||||
|
||||
#echo cmd=${cmd}
|
||||
|
||||
set -x
|
||||
|
||||
# capture
|
||||
${cmd}
|
||||
|
||||
# keep only files with paths under source tree
|
||||
# (don't want coverage for external libraries such as libstdc++ etc)
|
||||
${lcov} --extract ${outputstem}.info "${srcdir}/*" --output ${outputstem}2.info
|
||||
|
||||
# remove unit test dirs
|
||||
# (we're interested in coverage of our installed code, not of the unit tests that exercise it)
|
||||
${lcov} --remove ${outputstem}2.info '*/utest/*' --output ${outputstem}3.info
|
||||
|
||||
# generate .html tree
|
||||
mkdir -p ${builddir}/ccov/html
|
||||
${genhtml} --ignore-errors source --show-details --prefix ${srcdir} --output-directory ${builddir}/ccov/html ${outputstem}3.info
|
||||
|
||||
# also send report to stdout
|
||||
${lcov} --list ${outputstem}3.info
|
||||
|
|
@ -15,7 +15,7 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL pre
|
|||
endif()
|
||||
|
||||
if (NOT XO_SUBMODULE_BUILD)
|
||||
message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}")
|
||||
message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}")
|
||||
message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
|
||||
message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
|
||||
endif()
|
||||
|
|
@ -23,4 +23,5 @@ 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-project-macros)
|
||||
#include(xo_macros/xo-project-macros)
|
||||
include(xo_macros/xo_cxx) # not using v1 code-coverage; testing cmake-examples impl instead
|
||||
|
|
|
|||
110
docs/CMakeLists.txt
Normal file
110
docs/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# xo-stringliteral/docs/CMakeLists.txt
|
||||
|
||||
if (XO_SUBMODULE_BUILD)
|
||||
# in submodule build, rely on toplevel docs/CMakeLists.txt file instead
|
||||
else()
|
||||
# build docs starting from here only in standalone build.
|
||||
# otherwise use top-level doxygen setup instead.
|
||||
|
||||
set(ALL_LIBRARY_TARGETS xo_stringliteral) # todo: automate this from xo-cmake macros
|
||||
set(ALL_UTEST_TARGETS xo_stringliteral_ex1 ) # todo: automate this from xo-cmake macros
|
||||
|
||||
# look for doxygen executable
|
||||
find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED)
|
||||
message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}")
|
||||
|
||||
# look for sphinx-build executable
|
||||
find_program(SPHINX_EXECUTABLE NAMES sphinx-build REQUIRED)
|
||||
message("-- SPHINX_EXECUTABLE=${SPHINX_EXECUTABLE}")
|
||||
|
||||
set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
||||
|
||||
set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR})
|
||||
set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox)
|
||||
|
||||
set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html)
|
||||
|
||||
# .hpp files reachable from xo-stringliteral/include
|
||||
#
|
||||
# REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes
|
||||
#
|
||||
file(GLOB_RECURSE DOX_HPP_FILES_GLOB ${PROJECT_SOURCE_DIR}/include *.hpp)
|
||||
|
||||
set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html)
|
||||
set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html)
|
||||
#
|
||||
# sphinx .rst files reachable from cmake-examples/docs
|
||||
#
|
||||
# REMINDER: for reliability will need to re-run cmake when the set of .rst files changes
|
||||
#
|
||||
file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst)
|
||||
|
||||
set(SPHINX_RST_FILES index.rst install.rst lessons.rst flatstring-reference.rst flatstring-class.rst)
|
||||
|
||||
# TODO:
|
||||
# 1. move Doxyfile.in to xo-cmake project
|
||||
# 2. replace this command section with xo-cmake macro
|
||||
#
|
||||
configure_file(
|
||||
Doxyfile.in ${DOX_CONFIG_FILE}
|
||||
FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||
@ONLY)
|
||||
|
||||
set(DOX_DEPS ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} ${DOX_HPP_FILES_GLOB})
|
||||
|
||||
file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR})
|
||||
add_custom_command(
|
||||
OUTPUT ${DOX_INDEX_FILE}
|
||||
DEPENDS ${DOX_DEPS}
|
||||
COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
MAIN_DEPENDENCY ${DOX_CONFIG_FILE}
|
||||
COMMENT "Generating docs (doxygen)")
|
||||
|
||||
# To build this target
|
||||
# $ cmake --build .build -j -- doxygen
|
||||
# or
|
||||
# $ cd .build
|
||||
# $ make doxygen
|
||||
#
|
||||
add_custom_target(
|
||||
doxygen
|
||||
DEPENDS ${DOX_INDEX_FILE} ${DOX_DEPS}
|
||||
)
|
||||
|
||||
# root of sphinx doc tree
|
||||
set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(SPHINX_DEPS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} ${DOX_DEPS})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${SPHINX_INDEX_FILE}
|
||||
DEPENDS ${SPHINX_DEPS}
|
||||
COMMAND ${SPHINX_EXECUTABLE}
|
||||
-b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml
|
||||
${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating docs (sphinx) -> [${SPHINX_OUTPUT_DIR}]")
|
||||
|
||||
# make sphinx --> generate sphinx documentation
|
||||
#
|
||||
add_custom_target(
|
||||
sphinx
|
||||
DEPENDS ${SPHINX_INDEX_FILE})
|
||||
|
||||
# - html docs generated in build/docs/sphinx
|
||||
# - copy the doc tree to share/doc/xo_unit/html
|
||||
#
|
||||
# OPTIONAL: install directory tree if it exists,
|
||||
# but don't complain if it's missing
|
||||
install(
|
||||
DIRECTORY ${SPHINX_OUTPUT_DIR}
|
||||
FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}
|
||||
COMPONENT Documentation
|
||||
OPTIONAL)
|
||||
|
||||
# make docs --> generate sphinx documentation
|
||||
add_custom_target(
|
||||
docs
|
||||
DEPENDS sphinx)
|
||||
endif()
|
||||
2816
docs/Doxyfile.in
Normal file
2816
docs/Doxyfile.in
Normal file
File diff suppressed because it is too large
Load diff
1
docs/_static/README
vendored
Normal file
1
docs/_static/README
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
add any static {.html, .js, ..} files for sphinx to pickup here
|
||||
35
docs/conf.py
Normal file
35
docs/conf.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# 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 stringlit 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']
|
||||
60
docs/flatstring-class.rst
Normal file
60
docs/flatstring-class.rst
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
.. _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:
|
||||
|
||||
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:
|
||||
15
docs/flatstring-functions.rst
Normal file
15
docs/flatstring-functions.rst
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
.. _flatstring_functions:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
Flatstring Functions
|
||||
====================
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <xo/flatstring/flatstring.hpp>
|
||||
|
||||
.. doxygenfunction:: xo::flatstring_concat
|
||||
|
||||
.. doxygenfunction:: xo::flatstring_compare
|
||||
11
docs/flatstring-reference.rst
Normal file
11
docs/flatstring-reference.rst
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.. _flatstring-reference:
|
||||
|
||||
Flatstring Reference
|
||||
====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Flatstring Reference
|
||||
|
||||
flatstring-class
|
||||
flatstring-functions
|
||||
38
docs/index.rst
Normal file
38
docs/index.rst
Normal 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`
|
||||
56
docs/install.rst
Normal file
56
docs/install.rst
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
.. _install:
|
||||
|
||||
.. toctree
|
||||
:maxdepth: 2
|
||||
|
||||
Install
|
||||
=======
|
||||
|
||||
`xo-flatstring source`_ lives on github.
|
||||
|
||||
.. _xo-flatstring source: https://github.com/rconybea/xo-flatstring
|
||||
|
||||
Implementation relies on c++20 features (for example class-instances as template arguments).
|
||||
Tested with gcc 13.2
|
||||
|
||||
Include as 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
|
||||
44
docs/lessons.rst
Normal file
44
docs/lessons.rst
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
.. _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');
|
||||
}
|
||||
|
|
@ -1,38 +1,165 @@
|
|||
/* @file ex1.cpp */
|
||||
|
||||
#include "xo/stringliteral/stringliteral.hpp"
|
||||
#include "xo/stringliteral/stringliteral_iostream.hpp"
|
||||
#include "xo/stringliteral/string_view_concat.hpp"
|
||||
#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::stringliteral;
|
||||
using xo::flatstring;
|
||||
#ifdef WAITAMO
|
||||
using xo::stringliteral_compare;
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
#ifdef NOT_USING
|
||||
constexpr stringliteral s1("hello");
|
||||
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
|
||||
|
||||
//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;
|
||||
|
||||
#ifdef NOT_SUCCESSFUL
|
||||
constexpr auto s11 = stringlit_make("0", "1", "23", "456", "78");
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
#ifdef NOT_USING
|
||||
static_assert(stringliteral_compare(s1, s1) == 0);
|
||||
|
||||
cerr << s1 << endl;
|
||||
|
||||
constexpr stringliteral s2 = stringliteral_concat(stringliteral("hello"),
|
||||
stringliteral(", world"));
|
||||
|
||||
#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("world"));
|
||||
static_assert(s15.fixed_capacity == 13);
|
||||
static_assert(sizeof(s15) == 13);
|
||||
|
||||
constexpr auto s16 = xo::flatstring_concat("foo", "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 s2 = concat_v<hello, world>;
|
||||
static constexpr auto s3 = stringlit_make_v<hello, world>;
|
||||
|
||||
static constexpr string_view hello_world("hello world");
|
||||
|
||||
static_assert(s2 == hello_world);
|
||||
static_assert(s3 == hello_world);
|
||||
|
||||
cerr << hello_world << endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* end ex1.cpp */
|
||||
|
|
|
|||
414
include/xo/flatstring/flatstring.hpp
Normal file
414
include/xo/flatstring/flatstring.hpp
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
/** @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 **/
|
||||
///@{
|
||||
using traits_type = std::char_traits<char>;
|
||||
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;
|
||||
using reference = value_type &;
|
||||
using const_reference = const value_type &;
|
||||
using pointer = std::allocator_traits<allocator_type>::pointer;
|
||||
using const_pointer = std::allocator_traits<allocator_type>::const_pointer;
|
||||
using iterator = char *;
|
||||
using const_iterator = const char *;
|
||||
using reverse_iterator = char *;
|
||||
using const_reverse_iterator = const char *;
|
||||
///@}
|
||||
|
||||
/** @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_);
|
||||
}
|
||||
///@}
|
||||
|
||||
/** @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) { return value_[pos]; }
|
||||
constexpr const value_type & operator[](size_type pos) const { return value_[pos]; }
|
||||
///@}
|
||||
|
||||
/** @defgroup flatstring-iterators iterators **/
|
||||
///@{
|
||||
constexpr iterator begin() { return &value_[0]; }
|
||||
constexpr iterator end() { return this->last(); }
|
||||
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 this->last(); }
|
||||
constexpr reverse_iterator rend() { return &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 **/
|
||||
void clear() { 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 str **/
|
||||
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 **/
|
||||
constexpr flatstring & assign(const flatstring & x,
|
||||
size_type pos, size_type count = npos) {
|
||||
std::size_t i = 0;
|
||||
for (;
|
||||
i < std::min(std::min(count,
|
||||
std::max(x.capacity-1 - pos,
|
||||
0)),
|
||||
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;
|
||||
}
|
||||
///@}
|
||||
|
||||
// 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 { 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() {
|
||||
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 stringliteral 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 native or wrapped string literals
|
||||
*
|
||||
* Can mix stringliteral objects and native C-style string literals,
|
||||
* and still preserve constexpr-ness.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* constexpr auto s = stringliteral_concat(stringliteral("hello"),
|
||||
* ", ",
|
||||
* stringliteral("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) {
|
||||
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 string literals lexicographically.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* constexpr auto cmp = stringliteral_compare(stringliteral("foo"), stringliteral("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_));
|
||||
}
|
||||
|
||||
/** @brief 3-way compare for two stringliterals **/
|
||||
template <std::size_t N1,
|
||||
std::size_t N2>
|
||||
constexpr auto
|
||||
operator<=>(const flatstring<N1> & s1,
|
||||
const flatstring<N2> & s2) noexcept
|
||||
{
|
||||
return flatstring_compare(s1, s2);
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
/** end stringliteral.hpp **/
|
||||
37
include/xo/flatstring/flatstring_iostream.hpp
Normal file
37
include/xo/flatstring/flatstring_iostream.hpp
Normal 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 **/
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/** @file stringliteral.hpp
|
||||
*
|
||||
* Author: Roland Conybeare
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/** @class stringliteral
|
||||
*
|
||||
* @brief class to represent a literal string at compile time, for use as template argument
|
||||
**/
|
||||
template <std::size_t N>
|
||||
struct stringliteral {
|
||||
constexpr stringliteral() { if (N > 0) value_[0] = '\0'; }
|
||||
constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); }
|
||||
constexpr int size() const { return N; }
|
||||
|
||||
constexpr char const * c_str() const { return value_; }
|
||||
|
||||
char value_[N];
|
||||
};
|
||||
|
||||
/** @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>... >;
|
||||
|
||||
/** @brief concatenate string literals
|
||||
*
|
||||
* NOTE: this isn't constexpr in clang16
|
||||
**/
|
||||
template < typename... Ts>
|
||||
constexpr auto
|
||||
stringliteral_concat(Ts && ... args)
|
||||
{
|
||||
#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 char_type = char;
|
||||
|
||||
/** n1: total number of bytes used by arguments **/
|
||||
constexpr 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 size_t z1 = sizeof...(Ts);
|
||||
|
||||
/** n: number of chars in concatenated string. +1 for final null **/
|
||||
constexpr size_t n
|
||||
= (n1 / sizeof(char_type)) - z1 + 1;
|
||||
|
||||
stringliteral<n> result;
|
||||
size_t pos = 0;
|
||||
|
||||
auto detail_concat = [ &pos, &result ](auto && arg) {
|
||||
constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type);
|
||||
|
||||
std::copy_n(arg.c_str(), count, result.value_ + pos);
|
||||
pos += count;
|
||||
};
|
||||
|
||||
(detail_concat(args), ...);
|
||||
|
||||
//return stringliteral("");
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef NOT_USING
|
||||
template <std::size_t N1, std::size_t N2>
|
||||
constexpr auto
|
||||
stringliteral_compare(stringliteral<N1> && s1, stringliteral<N2> && s2)
|
||||
{
|
||||
return std::string_view(s1.value_) <=> std::string_view(s2.value_);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <std::size_t N1, std::size_t N2>
|
||||
constexpr auto
|
||||
stringliteral_compare(const stringliteral<N1> & s1, const stringliteral<N2> & s2)
|
||||
{
|
||||
return std::string_view(s1.value_) <=> std::string_view(s2.value_);
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/** end stringliteral.hpp **/
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/** @file stringliteral_iostream.hpp
|
||||
*
|
||||
* Author: Roland Conybeare
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stringliteral.hpp"
|
||||
#include <ostream>
|
||||
//#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/** @brief print stringliteral on stream os.
|
||||
*
|
||||
**/
|
||||
template <std::size_t N>
|
||||
void
|
||||
print_stringliteral (std::ostream & os, const stringliteral<N> & x) {
|
||||
os << x.c_str();
|
||||
}
|
||||
|
||||
/** @brief print stringliteral x on stream os.
|
||||
*
|
||||
* Example
|
||||
* @code
|
||||
* cout << stringliteral("foo"); // outputs "foo"
|
||||
**/
|
||||
template <std::size_t N>
|
||||
inline std::ostream &
|
||||
operator<< (std::ostream & os, const stringliteral<N> & x) {
|
||||
print_stringliteral(os, x);
|
||||
return os;
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
/** end stringliteral_iostream.hpp **/
|
||||
1
utest/.#flatstring_utest_main.cpp
Symbolic link
1
utest/.#flatstring_utest_main.cpp
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
roland@roly-desktop-23.717424:1712774400
|
||||
21
utest/CMakeLists.txt
Normal file
21
utest/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# xo-flatstring/utest/CMakeLists.txt
|
||||
|
||||
set(SELF_EXE utest.flatstring)
|
||||
set(SELF_SRCS
|
||||
flatstring_utest_main.cpp
|
||||
flatstring.test.cpp)
|
||||
|
||||
add_executable(${SELF_EXE} ${SELF_SRCS})
|
||||
xo_include_options2(${SELF_EXE})
|
||||
|
||||
add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE})
|
||||
#target_code_coverage(${SELF_EXE} AUTO ALL)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 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
|
||||
131
utest/flatstring.test.cpp
Normal file
131
utest/flatstring.test.cpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/** @file flatstring.utest.cpp **/
|
||||
|
||||
#include "xo/stringliteral/stringliteral.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
//#include <iostream>
|
||||
|
||||
namespace xo {
|
||||
using namespace std;
|
||||
|
||||
namespace ut {
|
||||
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);
|
||||
|
||||
/* verify range iteration visits contents in order */
|
||||
{
|
||||
size_t i = 0;
|
||||
for (char ch : str) {
|
||||
INFO(XTAG(i));
|
||||
|
||||
CHECK(ch == text[i]);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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, text) \
|
||||
constexpr flatstring name{text}; \
|
||||
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.rbegin() != nullptr); \
|
||||
static_assert(name.rend() != nullptr); \
|
||||
static_assert(name.crbegin() != nullptr); \
|
||||
static_assert(name.crend() != nullptr); \
|
||||
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); \
|
||||
REQUIRE(name.fixed_capacity == strlen(text)+1); \
|
||||
REQUIRE(name.capacity() == strlen(text)); \
|
||||
REQUIRE(name.size() == strlen(text)); \
|
||||
REQUIRE(name.length() == strlen(text)); \
|
||||
REQUIRE(strcmp(name.c_str(), text) == 0); \
|
||||
REQUIRE(strcmp(name, text) == 0); \
|
||||
static_assert(string_view(name) == string_view(name)); \
|
||||
|
||||
|
||||
|
||||
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, "h");
|
||||
LITERAL_TEST_BODY(s2, "he");
|
||||
LITERAL_TEST_BODY(s3, "hel");
|
||||
LITERAL_TEST_BODY(s4, "hell");
|
||||
LITERAL_TEST_BODY(s5, "hello");
|
||||
LITERAL_TEST_BODY(s6, "hello,");
|
||||
LITERAL_TEST_BODY(s7, "hello, ");
|
||||
LITERAL_TEST_BODY(s8, "hello, w");
|
||||
LITERAL_TEST_BODY(s9, "hello, wo");
|
||||
LITERAL_TEST_BODY(s10, "hello, wor");
|
||||
LITERAL_TEST_BODY(s11, "hello, worl");
|
||||
LITERAL_TEST_BODY(s12, "hello, world");
|
||||
LITERAL_TEST_BODY(s13, "hello, world!");
|
||||
|
||||
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);
|
||||
} /*TEST_CASE(flatstring)*/
|
||||
|
||||
} /*namespace ut*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/** end flatstring.utest.cpp **/
|
||||
6
utest/flatstring_utest_main.cpp
Normal file
6
utest/flatstring_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/* @file flatstring_utest_main.cpp */
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
/* end flatstring_utest_main.cpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue