🟩 Primitive types, asserts, compiler macros, Function, Span, Result
SaneCppFoundation.h is a library that provides many fundamental type definitions and types widely used by other libraries.
As this is included and needed by almost every other library, it tries to keep bloat to the bare minimum.
Detailed documentation is in the Foundation topic.
Dependencies
- Dependencies: (none)
- All dependencies: (none)

Features
Classes
| Class | Description |
| SC::Span | View over a contiguous sequence of items (pointer + size in elements). |
| SC::StringSpan | An read-only view over a string (to avoid including Strings library when parsing is not needed). |
| SC::StringPath | Pre-sized char array holding enough space to represent a file system path. |
| SC::Result | An ascii string used as boolean result. SC_TRY macro forwards errors to caller. |
| SC::Function | Wraps function pointers, member functions and lambdas without ever allocating. |
| SC::Deferred | Executes a function at end of current scope (in the spirit of Zig defer keyword). |
| SC::OpaqueObject | Hides implementation details from public headers (static PIMPL). |
| SC::UniqueHandle | Move only handle that has a special tag value flagging its invalid state. |
Macros
Compiler Macros Preprocessor macros to detect compiler and platform features.
Type Traits
Type Traits (EnableIf, AddPointer, etc.)
Utilities
| Class | Description |
| SC::Assert | Functions and macros to assert, exit() or abort() and capture backtraces. |
| SC::AlignedStorage | A buffer of bytes with given alignment. |
Status
🟩 Usable
The library is very simple it it has what is needed so far by the other libraries.
Description
There is an hard rule in the library Principles not to include system and compiler headers in public headers.
Foundation provides all primitive types to be used in headers and classes like SC::UniqueHandle, SC::OpaqueObject, SC::AlignedStorage to encourage static PIMPL in order to hide platform specific implementation details everywhere.
Function
Wraps function pointers, member functions and lambdas without ever allocating.
- Template Parameters
-
| FuncType | Type of function to be wrapped (Lambda, free function or pointer to member function) |
- Note
- Size of lambdas or less than LAMBDA_SIZE (currently
2 * sizeof(void*)).
If lambda is bigger than LAMBDA_SIZE the constructor will static assert.
Example:
struct SomeClass
{
float memberValue = 2.0;
int memberFunc(float a) { return static_cast<int>(a + memberValue); }
};
struct SomeFunctor
{
float memberValue = 2.0;
int operator()(float a) { return static_cast<int>(a + memberValue); }
};
int someFunc(float a) { return static_cast<int>(a * 2); }
struct BigClass
{
};
void SC::FunctionTest::functionDocumentationSnippet()
{
SomeClass someClass;
func = &someFunc;
func.bind<SomeClass, &SomeClass::memberFunc>(someClass);
func = [](float a) -> int { return static_cast<int>(a + 1.5); };
func = SomeFunctor{2.5};
}
Deferred
Executes a function at end of current scope (in the spirit of Zig defer keyword).
- Template Parameters
-
| F | The lambda / function to execute |
Example:
HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE, FALSE, processId);
if (processHandle == nullptr)
{
return false;
}
[&]
{
CloseHandle(processHandle);
processHandle = nullptr;
});
OpaqueObject
Hides implementation details from public headers (static PIMPL).
Opaque object avoids the heap allocation that often comes with PIMPL, allowing to hide OS specific details from public headers. User declares size in bytes of a struct in the header but the structure can be defined in an implementation .cpp file. Choosing a size that is too small will generate a static_assert that contains in the error message the minimum size to use. Up to 4 functions will need to be defined to avoid linker errors (construct, destruct, moveConstruct, moveAssign). These functions are meant to be defined in a .cpp file that will know how to construct Object, as it can see its definition.
- Template Parameters
-
| Definition | Pass in a custom Definition declaring Sizes and alignment on different platforms |
Example:
... in the header file
struct FileSystemWatcher
{
private:
struct Internal;
struct InternalDefinition
{
static constexpr int Windows = 3 * sizeof(void*);
static constexpr int Apple = 42 * sizeof(void*);
static constexpr int Linux = sizeof(void*) * 4;
static constexpr int Default = Linux;
static constexpr size_t Alignment = alignof(void*);
using Object = Internal;
};
public:
using InternalOpaque = OpaqueObject<InternalDefinition>;
private:
InternalOpaque internal;
... in .cpp file Declare Internal struct with all platform specific details (requiring OS specific headers).
#include "../../FileSystemWatcher/FileSystemWatcher.h"
#include "../../Foundation/Deferred.h"
#include "FileSystemWatcherThreading.h"
#include <CoreServices/CoreServices.h>
#include <AvailabilityMacros.h>
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
#include "FSEventsIOS.h"
#endif
struct SC::FileSystemWatcher::Internal
{
FileSystemWatcher* self = nullptr;
CFRunLoopRef runLoop = nullptr;
CFRunLoopSourceRef refreshSignal = nullptr;
FSEventStreamRef fsEventStream = nullptr;
FSWThread pollingThread;
Result signalReturnCode = Result(false);
FSWEventObject refreshSignalFinished;
FSWMutex mutex;
EventLoopRunner* eventLoopRunner = nullptr;
Notification notification;
FolderWatcher* watcher;
FSWAtomicBool closing;
Declare only two of the four functions to avoid linker errors.
template <>
void SC::FileSystemWatcher::InternalOpaque::construct(Handle& buffer)
{
placementNew(buffer.reinterpret_as<Object>());
}
template <>
void SC::FileSystemWatcher::InternalOpaque::destruct(Object& obj)
{
obj.~Object();
}
UniqueHandle
Move only handle that has a special tag value flagging its invalid state.
Typically used to wrap Operating System specific handles.
- Template Parameters
-
| Definition | A struct declaring handle type, release function and invalid handle value. |
Example:
... definition in header
struct SC_FILE_EXPORT FileDescriptorDefinition
{
using Handle = int;
static Result releaseHandle(Handle& handle);
static constexpr Handle Invalid = -1;
};
... derive from it
struct SC_FILE_EXPORT FileDescriptor : public UniqueHandle<detail::FileDescriptorDefinition>
{
using UniqueHandle::UniqueHandle;
...declaration in .cpp file
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
namespace
{
static SC::TimeMs fileDescriptorPosixTimespecToTimeMs(
const timespec& timespecValue)
{
static_cast<SC::int64_t>(timespecValue.tv_nsec / (1000 * 1000))};
}
{
if (S_ISREG(mode))
return SC::FileDescriptorEntryType::File;
if (S_ISDIR(mode))
return SC::FileDescriptorEntryType::Directory;
if (S_ISLNK(mode))
return SC::FileDescriptorEntryType::SymbolicLink;
return SC::FileDescriptorEntryType::Other;
}
{
fileStat = {};
fileStat.
entryType = fileDescriptorPosixEntryTypeFromMode(pathStat.st_mode);
fileStat.
accessedTime = fileDescriptorPosixTimespecToTimeMs(
#if __APPLE__
pathStat.st_atimespec
#else
pathStat.st_atim
#endif
);
fileStat.
modifiedTime = fileDescriptorPosixTimespecToTimeMs(
#if __APPLE__
pathStat.st_mtimespec
#else
pathStat.st_mtim
#endif
);
#if __APPLE__
fileStat.
creationTime = fileDescriptorPosixTimespecToTimeMs(pathStat.st_birthtimespec);
#endif
}
}
SC::Result SC::detail::FileDescriptorDefinition::releaseHandle(Handle& handle)
{
if (::close(handle) != 0)
{
return Result::Error(
"FileDescriptorDefinition::releaseHandle - close failed");
}
return Result(true);
}
...usage
#include <fcntl.h>
{
SC_TRY(filePath.assign(
"someFile.txt"));
const int flags = O_RDWR | O_CREAT | O_TRUNC;
const int access = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
SC_TRY(myDescriptor.assign(nativeFd));
SC_TRY(otherDescriptor.close());
otherDescriptor.detach();
if (otherDescriptor.isValid())
{
}
}
Blog
Some relevant blog posts are:
Roadmap
🟦 Complete Features:
- Things will be added as needed
Statistics
| Type | Lines Of Code | Comments | Sum |
| Headers | 1136 | 688 | 1824 |
| Sources | 274 | 50 | 324 |
| Sum | 1410 | 738 | 2148 |