base¶
The generic utilities every other module may use. Namespace core, headers directly in
src/core/, target core::base. It builds everywhere core-cpp does, single-threaded
WebAssembly included, and links Threads::Threads except there.
Imported from contour's src/crispy at 6777ff05, with crispy's namespace renamed core, from
fastcached's src/FastCache/Core at ee71f868, and Generator.hpp from endo's
src/platform/Generator.hpp at f774a210.
| Header | What it has |
|---|---|
<core/Assert.hpp> |
Require() and Guarantee(), which report through a replaceable fail handler (core::setFailHandler()) and abort; core::todo() and core::unreachable() |
<core/Base64.hpp> |
core::base64::encode() and decode(), batch and streaming |
<core/Config.hpp> |
generated by the configure: the version and the CORE_CPP_WITH_* switches, each 0 or 1 |
<core/Deferred.hpp> |
core::Deferred<T>, a value initialised once, after its owner |
<core/Defines.hpp> |
CORE_PACKED, CORE_REQUIRES, CORE_CONSTEVAL, CORE_CONSTEXPR |
<core/Environment.hpp> |
core::Environment, the injectable reader of environment variables; LiveEnvironment, CachingEnvironment, defaultEnvironment(); setProcessEnvironmentVariable() and unsetProcessEnvironmentVariable(), the writer |
<core/Escape.hpp> |
core::escape(), unescape() and escapeMarkdown() for byte strings |
<core/FNV.hpp> |
core::FNV, the FNV-1a hash |
<core/Flags.hpp> |
core::Flags<Enum>, a type-safe set of bit flags, and its std::formatter |
<core/Generator.hpp> |
core::Generator<T>, a lazy, single-pass coroutine range; see Generator below |
<core/Overloaded.hpp> |
core::Overloaded, lambdas combined into one visitor |
<core/Profiling.hpp> |
the CORE_ZONE_*, CORE_FRAME_MARK*, CORE_THREAD_NAME and CORE_PLOT macros; see Profiling |
<core/Ranges.hpp> |
core::findOrNull() and findIfOrNull(), which answer a pointer; core::ranges::Iota and FoldLeft, the standard facility where the library has it and a fallback where it does not |
<core/Times.hpp> |
core::times(), a counted range to iterate or to pipe a callable into |
<core/UserInfo.hpp> |
the calling user's password-database entry (getpwuid_r()); none on Windows or under Emscripten |
<core/Utils.hpp> |
string splitting, trimming, joining and case mapping, toInteger(), hex strings, ${VAR} substitution, nextPowerOfTwo(), views::enumerate(), threadName() |
Generator¶
<core/Generator.hpp> has core::Generator<T>, a lazy, single-pass range a coroutine fills with
co_yield, imported from endo (src/platform/Generator.hpp at f774a210). It moved here from
core::async in Task A5b: core::async::Generator would read as an asynchronous,
co_await-able stream, and this one is synchronous, needing nothing but the standard library and
the compiler's coroutine language support — the one header here that does.
- It is
std::generator<T>where the standard library has<generator>and is not libstdc++, and otherwisecore::detail::GeneratorFallback<T>, a small implementation over<coroutine>. libc++ has no<generator>yet (emsdk 3.1.56 ships libc++ 17), and GCC 14 reports a nullcoroutine_handleinside libstdc++'s ownstd::generatorat-O2(-Wnull-dereference), which the zero-warning policy makes an error. In practice MSVC usesstd::generatorand everything else the fallback. - The choice is read from
<version>, which the header includes first, so every translation unit makes the same one. endo's copy tested__cpp_lib_generatorbefore including anything, so the answer depended on what a file included first; a virtual function returning aGeneratorcould then have two return types in one program. CORE_GENERATOR_FORCE_FALLBACK, defined the same way in every translation unit, selects the fallback everywhere.- The fallback is always defined and is tested on every platform. Each yielded value lives in the coroutine frame until the next increment, so yield owning values. Breaking out of the loop destroys the suspended frame.
Assertions that log¶
fatal() and SoftRequire() were part of crispy's Assert.hpp. They report through the log
store, which core::base may not depend on, so they are in log: <core/log/Assert.hpp>,
as core::log::fatal() and the SoftRequire() macro.
The environment seam¶
Code that reads an environment variable takes a core::Environment const& rather than calling
getenv(), so a test hands it a core::testing::FakeEnvironment
(<core/testing/Environment.hpp>, in testing) instead of changing the process's
environment. core::platform::EnvironmentProvider (platform) is a second such
seam; core-cpp#7 merges them.
A process that must change its own environment, because a child process inherits it, calls
core::setProcessEnvironmentVariable() and core::unsetProcessEnvironmentVariable(), never
setenv(). On POSIX they never edit a block a reader may be walking: they publish a new
environ block with one store, under the lock LiveEnvironment reads under, and never free what
they published, because getenv() in another library reads without that lock. Each write that
changes something so costs one block of pointers, which suits the few writes a process makes to
its own environment; a write that changes nothing publishes nothing. On Windows they are
SetEnvironmentVariableA(), which the CRT's getenv() does not see; read through
LiveEnvironment. Both refuse an empty name and a name with =
(std::errc::invalid_argument). They take a lock and allocate, so they are not for use between
fork() and exec(): give the child its environment through execve() instead.
Under Emscripten¶
All of core::base builds for single-threaded WebAssembly. There threadName() answers an empty
string, the password database has no entries, and LiveEnvironment reads Emscripten's own
environment, which under node is not the host's.
crispy's renderer-side half (ring buffers, LRU caches, the aligned allocator and the like) stays in contour: it has one consumer. A file moves into core-cpp when a second project needs it.