Utility classes to make working with C APIs safer in C++
cinter is a lightweight, header-only C++ library that provides zero-overhead (or near-zero-overhead) wrapper classes to simplify and improve safety when working with C APIs. The library addresses common pain points when interfacing with C code, such as handling null pointers in strings and dealing with sentinel-based error codes.
The library consists of two main components:
safe_string: A wrapper for C-style strings that safely handles null pointers by treating them as empty stringssentinel_result: A template class for wrapping return values that use sentinel values or ranges to indicate errors (e.g.,-1,NULL,INVALID_HANDLE_VALUE)
The library is built around several core principles:
- Zero-overhead abstraction: The wrapper classes are designed to compile down to the same machine code as raw C API calls in optimized builds
- Type safety: Leverage C++ type system to catch errors at compile-time when possible
- Explicit error handling: Make error states visible and easy to check without exceptions
- Header-only: No linking required, just include and use
- Modern C++ idioms: Uses
constexpr,[[nodiscard]], and other modern C++ features while maintaining simplicity - Non-invasive: Works alongside existing C code without requiring refactoring
- ✅ Header-only library
- ✅ Zero runtime overhead in release builds
- ✅
constexprsupport for compile-time operations - ✅
safe_stringtreats null as empty, matching many C library conventions - ✅ Only uses the standard library with no external dependencies
- ✅ Full character type support (
char,wchar_t,char8_t,char16_t,char32_t) - ✅ Compatible with standard library types (
std::string,std::string_view) - ✅ Range-based for loop support
- ✅ BSD 3-Clause License
The basic_safe_string<Char> class template wraps a pointer to a character array (C-string) and treats null pointers as empty strings.
Char: Character type (default:char)
basic_safe_string has the following type aliases:
const_iterator= Pointer to constant character type
constexpr bool is_null() const: Returnstrueif the wrapped pointer is nullexplicit constexpr operator bool() const: Returnstrueif the wrapped pointer is not nullconstexpr const Char* c_str() const: Returns the wrapped pointer, or an empty string if nullstd::basic_string<Char> string() const: Converts tostd::stringconstexpr std::basic_string_view<Char> view() const: Returns astd::string_viewconstexpr const_iterator begin() const: Iterator support for range-based loopsconstexpr const_iterator end() const: Iterator support for range-based loops
All comparison operators (==, !=, <, <=, >, >=) are supported and compare the string contents.
The following basic_safe_string aliases are provided for common character types:
safe_string=basic_safe_string<char>safe_wstring=basic_safe_string<wchar_t>safe_u8string=basic_safe_string<char8_t>safe_u16string=basic_safe_string<char16_t>safe_u32string=basic_safe_string<char32_t>
The sentinel_result<T, sentinel, SuccessComp> class template wraps return values from C APIs that use special sentinel values to indicate errors.
T: The value type returned by the C APIsentinel: The sentinel value that indicates success or failure (default:0)SuccessComp: Comparator to determine success (default:std::equal_to<T>)
value_type value() const: Returns the wrapped valuebool has_error() const: Returnstrueif the value indicates an errorexplicit operator bool() const: Returnstrueif the operation succeededoperator value_type() const: Implicit conversion to the underlying type
#include <iostream>
#include "cinter.hpp"
extern "C" const char* get_user_name(); // May return nullptr
int main()
{
// name is safe to use even if get_user_name() returns nullptr.
cinter::safe_string name = get_user_name();
std::cout << "User: " << name.c_str() << std::endl;
// Check if the pointer was null
if (name.is_null()) {
std::cerr << "Warning: No user name available\n";
}
// Convert to std::string
std::string cpp_name = name.string();
// Use with string_view
auto view = name.view();
// Range-based for loop
for (char c : name) {
std::cout << c;
}
return 0;
}
#include "cinter.hpp"
#include <iostream>
#include <windows.h>
// Default value of zero (S_OK) is the success value.
using SentinelHResult = sentinel_result<HRESULT>;
int main()
{
SentinelHResult hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (hr.has_error()) {
std::cerr << "COM initialization failed: 0x"
<< std::hex << hr.value() << std::endl;
return 1;
}
// Implicit conversion to bool
if (hr) {
std::cout << "COM initialized successfully\n";
}
CoUninitialize();
return 0;
}
This project is licensed under the BSD 3-Clause License. See the license text in the source files for details.
Copyright (c) 2025, Kevin Hall. All rights reserved.
Contributions are welcome! Whether you're fixing bugs, improving documentation, or proposing new features, your help is appreciated.
- Report Issues: Found a bug or have a suggestion? Open an issue on GitHub
- Submit Pull Requests: Fork the repository, make your changes, and submit a PR
- Improve Documentation: Help make the documentation clearer and more comprehensive
- Share Use Cases: Let us know how you're using the library
- Maintain the zero-overhead philosophy
- Keep the code header-only
- Ensure compatibility with C++17 and later
- Add tests for new features
- Follow the existing code style (see source files)
- Update documentation for API changes
We look forward to your contributions!