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/optional
with 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/).
- Filesystem 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 13.1 | Clang 13+ |
Fedora Linux 36 | Clang 13+ |
- 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 build
For 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.test
The 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(promise::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_null
andbyte_size
wrappers 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.
Promise tags are used to:
- Prevent misuse and differentiate constructors, e.g.
promise::has_capacity
,promise::is_sorted
andpromise::null_terminated
. - Select a different overload, e.g.
.value()
throws and.value(promise::has_value)
asserts. - Select a more performant overload, e.g.
promise::no_overlap
. - Bypass expensive checks, e.g.
promise::is_utf8
.
Promises are not used when there is an implicit promise 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_*()
.
Promises 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
T
from 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>(...)
.
A promise 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, promise::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()
:
- Promises where the check isn't expensive or is easily optimized away.
Example:
.at(index, promise::within_bounds)
or.front(promise::not_empty)
.
Checked in non-optimized builds with snn_should()
:
- Promises where the check is expensive or not easily optimized away.
not_null
¬_zero
wrappers.as_*()
functions, where there is an implicit promise 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
,UInt
orUnsignedInt
. - 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::_template
orhttp::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_BOOL
orSNN_INT128_BOOL
.
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.