A modern, safer alternative to the C++ Standard Library (for Linux/BSD).
- Immutable and mutable
array_view, including[c]strview. - Safe and efficient string parsing with ranges (example).
- Strings with and without Small String Optimization (
str/strbuf). - Vector with optional small capacity and trivially relocatable optimization (
vec). - Date and time library with IANA time zone database (time/).
result/optionalwith reference support..range()/.view(...)member functions to discourage the use of iterators.- Validation member functions like
.all(...)and.any(...)on iterable containers. - Safe integral class and a decimal class with a static/dynamic scale (num/).
- Command line options parsing (env/).
- File system library (file/).
- Fast JSON parsing (json/).
- Go-like build-tool with fuzzing support.
- And much more...
- Safe by default or even harder to shoot yourself in the foot (see Safety).
- Readable and well tested code.
- High performance without sacrificing on the points above.
- Replacing the entire C++ Standard Library. This library uses the C++ Standard Library
throughout where appropriate, e.g. type traits, concepts, algorithms and utility functions
like
std::move.
- C++20
- 64-bit
This library currently targets POSIX and is developed and tested on:
| Operating system | Compiler |
|---|---|
| FreeBSD 14.2 | Clang 18+ |
| Fedora Linux 41 | Clang 19+ |
- No wide character support (by design).
- No Windows support (most code outside of
file::,random::andprocess::should work)1. - No grapheme support1.
- No big-endian support1.
| Path | Description | |
|---|---|---|
| algo/ | Range algorithms | Readme |
| app/ | Reserved namespace for applications | Readme |
| ascii/ | ASCII string functions | Readme |
| base64/ | Base64 encoding and decoding | Readme |
| chr/ | Character (char) functions |
Readme |
| cmp/ | Comparison functions | Readme |
| country/ | Country codes and names | Readme |
| crypto/ | Cryptographic hashes and derivation functions | Readme |
| encoding/ | Encoding schemes | Readme |
| env/ | Environment variables and command line options | Readme |
| file/ | File system | Readme |
| fmt/ | Formatting | Readme |
| fn/ | Function objects | Readme |
| generic/ | Generic errors | Readme |
| hex/ | Hex encoding and decoding | Readme |
| html/ | HTML encoding | Readme |
| io/ | I/O concepts | Readme |
| json/ | JSON encoding and decoding | Readme |
| map/ | Sorted and unsorted maps | Readme |
| math/ | Math functions | Readme |
| mem/ | Allocators and memory functions | Readme |
| num/ | Numerical classes | Readme |
| pair/ | Pair functions and classes | Readme |
| pcre/ | Perl Compatible Regular Expressions | Readme |
| pool/ | Pool containers | Readme |
| process/ | Shell commands and process spawning | Readme |
| random/ | High-quality random data | Readme |
| range/ | Ranges and range views (including [c]strrng aliases) |
Readme |
| regex/ | Regular expressions | Readme |
| set/ | Sorted and unsorted sets | Readme |
| stream/ | Stream classes and concepts | Readme |
| string/ | String functions and ranges | Readme |
| system/ | System error category and system functions | Readme |
| thread/ | Thread functions | Readme |
| time/ | Date and time (including IANA Time Zone Database) | Readme |
| unicode/ | Unicode constants and functions | Readme |
| url/ | URL encoding | Readme |
| utf8/ | UTF-8 functions and ranges | Readme |
| array.hh | Aggregate array (always initialized) | Example/Tests |
| array_view.fwd.hh | Array view (forward declare) and [c]strview aliases |
|
| array_view.hh | Array view with [c]strview specializations |
Example/Tests |
| contiguous_interface.hh | Contiguous interface | Example/Tests |
| core.hh | Core functionality | Example/Tests |
| debug.hh | Debug functions and macros | Example/Tests |
| defer.hh | Call a function on destruction | Example/Tests |
| error_code.hh | Error category and error code | Example/Tests |
| exception.hh | Exception and throw_or_abort(...) function |
|
| formatter.hh | Formatter primary template | |
| fuzz.hh | Fuzzer entry point | |
| main.hh | Application entry point | |
| make_range.hh | Make range function | Example/Tests |
| null_term.hh | Null-terminated non-null pointer wrapper | Example/Tests |
| optional.fwd.hh | Optional (forward declare) | |
| optional.hh | Optional (result without error code) |
Example/Tests |
| optional_index.hh | Optional index | Example/Tests |
| result.hh | Result with a value/reference or an error code | Example/Tests |
| size_prefixed_string_literal.hh | Size prefixed string literal | Example/Tests |
| strcore.fwd.hh | String (forward declare), concepts and str[buf] aliases |
|
| strcore.hh | String (str[buf]) and concat(...) function |
Example/Tests |
| unittest.hh | Unit test entry point and snn_require macros |
|
| val_or_ref.hh | Reassignable value or reference | Example/Tests |
| vec.hh | Vector with optional small-capacity | Example/Tests |
The snncpp Go-like build-tool can build C++ projects that follow the same naming convention and
directory structure as snn-core. It understands simple preprocessing directives (example)
and will link with libraries listed in #include comments (example).
The build-tool executable is snn (by default), run it without any arguments to see
what commands are available:
$ snn
Usage: snn <command> [arguments]
Commands:
build Build one or more applications
gen Generate a makefile for one or more applications
run Build and run a single application with optional arguments
runall Build and run one or more applications
For more information run a command without arguments, e.g.:
snn buildFor example, to run all unit tests in the pair/ subdirectory:
$ snn runall --verbose pair/*.test.cc
clang++ --config ./.clang -iquote ../ -c -o pair/common.test.o pair/common.test.cc
clang++ --config ./.clang -o pair/common.test pair/common.test.o -L/usr/local/lib/
clang++ --config ./.clang -iquote ../ -c -o pair/core.test.o pair/core.test.cc
clang++ --config ./.clang -o pair/core.test pair/core.test.o -L/usr/local/lib/
./pair/common.test
./pair/core.testThe Getting started guide for the build-tool shows how to use snn-core.
- Ranges and views are preferred over iterators.
- Views will not bind to temporaries by default.
- String ranges (
c[strrng]) make string parsing/validation safe and very efficient (example). - Hidden undefined behavior (UB) is minimized, e.g.
.front()returns an optional and.front(assume::not_empty)explicitly shows that we know that the container is not empty. - The use of operators like
*expr(indirection/dereference),expr->(member access via pointer) andexpr[...](subscript) in user code should be rare. - No uninitialized containers (unless explicitly asked for).
not_nullandbyte_sizewrappers for low level memory operations.- Consistent naming, e.g.
.size()for byte size and.count()for element count. .size()is only available whensizeof(value_type) == 1,.byte_size()is always available (on contiguous containers).- No silent narrowing, e.g.
.value_or(...). - Consistent brace initialization.
Assume tags are used to:
- Prevent misuse and differentiate constructors, e.g.
assume::has_capacity,assume::is_sortedandassume::null_terminated. - Select a different overload, e.g.
.value()throws and.value(assume::has_value)asserts. - Select a more performant overload, e.g.
assume::no_overlap. - Bypass expensive checks, e.g.
assume::is_utf8.
Assume tags are not used when there is an implicit assumption that can be checked with the
snn_should() macro, for example:
- Wrapping a pointer with
not_null. - Wrapping an integral with
not_zero. - Wrapping functions, e.g.
ascii::as_lower()andjson::stream::as_*().
Assume tags are not used when the intent is clear and contained in a single statement, even if the arguments can't be validated, for example:
- Constructing an object
Tfrom a data pointer and a sizeT{data, size}. - Copying memory with
mem::raw::copy(...). - Reading a fixed byte count from a pointer with
mem::raw::load<Int>(...).
An assume tag is recommended when a static count is part of the type, e.g. array_view<..., Count>.
Here T{data} is error prone if the count isn't included in the statement, whereas
T{data, assume::has_capacity} is less so.
Compiling with the snn_assert() macro disabled is not recommended, especially for public
production builds.
If NDEBUG is defined as a macro name then snn_assert() does nothing (optimized build or not).
In non-optimized builds snn_assert() is another name for assert().
In optimized builds snn_assert() simply calls __builtin_trap() if the condition is false.
In non-optimized builds snn_should() is another name for snn_assert().
In optimized builds snn_should() does nothing.
Checked with snn_assert():
- Assume tags where the check isn't expensive or is easily optimized away.
Example:
.at(index, assume::within_bounds)or.front(assume::not_empty).
Checked in non-optimized builds with snn_should():
- Assume tags where the check is expensive or not easily optimized away.
not_null¬_zerowrappers.as_*()functions, where there is an implicit assumption that the value is valid.
Never checked:
- Iterator invalidation.
array_view<T, ...>invalidation.
Using sanitizers in development help to catch bugs that this library can't protect against. The
build-tool has a --sanitize option that enables sanitizers.
It is assumed that all types are "sane":
- If a type is copy constructible it must also be move constructible.
- If a type is copy assignable it must also be move assignable.
- If a type is move constructible it must also be nothrow move constructible.
- If a type is move assignable it must also be nothrow move assignable.
This can be checked with the sane concept, but is not enforced. The worst thing that can happen if
a type is not "sane" is that a function with a noexcept specifier throws and std::terminate() is
called.
It is assumed that the largest addressable memory block is always less than 128 PiB (57-bit virtual
address space). 128 PiB is less than constant::limit<usize>::max / 100.
This means that code like the following could never overflow:
auto decode(const cstrview s)
{
const usize decoded_size = s.size() * 4;
...
}Code like the following could theoretically overflow:
auto decode(const cstrview s)
{
const usize decoded_size = s.size() * 200;
...
}A comment that includes the string "57-bit-virtual-address-space" should be added everywhere this assumption is made.
All identifiers should be in snake_case with the following exceptions:
- Template parameters should be in upper CamelCase (capitalized first letter).
Example:
I,Int,UIntorUnsignedInt. - Type declarations and constexpr variables that directly depend on template parameters can also
be in CamelCase.
Example:
using UInt = std::make_unsigned_t<Int>. - Private/protected member functions and variables should have an underscore suffix.
Example:
value_,names_orerror_count_. - If a reserved keyword can't be avoided or if it's simply the best name for an identifier, it
must have an underscore prefix.
Example:
fn::_not,html::element::type::_templateorhttp::method::_delete. - Function-like macros should be lowercase with "snn_" prefix.
Example:
snn_assert()orsnn_should(). - Macro constants should be uppercase with "SNN_" prefix.
Example:
SNN_ASSERT_ENABLEDorSNN_INT128_ENABLED.
Generated API documentation is planned. Until then the code itself (which is pretty readable outside
of the detail namespace) and examples/unit tests should hopefully be enough to use this library.
See LICENSE.md. Copyright © 2022 Mikael Simonsson.