A modern, header-only C++20 configuration library with compile-time type safety, zero macros, IDE autocompletion, and pluggable serialization.
- Compile-time type safety - All type errors caught at compile time
- Zero macros - Pure C++20 templates and concepts
- IDE-friendly - Full autocompletion:
config.Get<settings::ServerPort>() - Hierarchical configuration - Dot-notation paths create nested structures
- Environment variable overrides - Production-friendly configuration
- Validation - Built-in validators with custom extensions
- Schema migration - Automatically adds new settings to existing files
- Mockable - GMock-compatible interfaces for unit testing
- Pluggable serialization - Flat
.confdefault, JSON opt-in, extensible to YAML/TOML - Thread-safe - Opt-in
MultiThreadedPolicywith reader-writer locking (zero overhead by default)
#include <cppfig/cppfig.h>
namespace settings {
struct ServerPort {
static constexpr std::string_view path = "server.port";
static constexpr std::string_view env_override = "SERVER_PORT"; // Optional
using value_type = int;
static auto default_value() -> int { return 8080; }
static auto validator() -> cppfig::Validator<int> { // Optional
return cppfig::Range(1, 65535);
}
};
struct ServerHost {
static constexpr std::string_view path = "server.host";
using value_type = std::string;
static auto default_value() -> std::string { return "localhost"; }
};
struct LogLevel {
static constexpr std::string_view path = "logging.level";
using value_type = std::string;
static auto default_value() -> std::string { return "info"; }
static auto validator() -> cppfig::Validator<std::string> {
return cppfig::OneOf<std::string>({"debug", "info", "warn", "error"});
}
};
} // namespace settings// Group settings into a schema
using MySchema = cppfig::ConfigSchema<
settings::ServerPort,
settings::ServerHost,
settings::LogLevel
>;
int main() {
// Create configuration manager (.conf format by default)
cppfig::Configuration<MySchema> config("config.conf");
// Load (creates file with defaults if missing)
auto status = config.Load();
if (!status.ok()) {
std::cerr << "Error: " << status.message() << std::endl;
return 1;
}
// Type-safe access with IDE autocompletion
int port = config.Get<settings::ServerPort>();
std::string host = config.Get<settings::ServerHost>();
// Modify with validation
status = config.Set<settings::ServerPort>(9000);
if (!status.ok()) {
std::cerr << "Validation error: " << status.message() << std::endl;
}
// Save changes
config.Save();
return 0;
}server.port = 8080
server.host = localhost
logging.level = info
Using JSON instead?
#include <cppfig/cppfig.h>
#include <cppfig/json.h> // opt-in
cppfig::Configuration<MySchema, cppfig::JsonSerializer> config("config.json");Enable with -DCPPFIG_ENABLE_JSON=ON or vcpkg feature "json".
See Serializers for details.
- C++20 compiler (GCC 11+, Clang 14+)
- No external dependencies for the core library
| Dependency | Feature | CMake Option |
|---|---|---|
| nlohmann/json | JSON serializer | CPPFIG_ENABLE_JSON |
cp -r cppfig/src/cppfig your_project/include/add_subdirectory(cppfig)
target_link_libraries(your_target PRIVATE cppfig)
# Optional: enable JSON support
set(CPPFIG_ENABLE_JSON ON)| Guide | Description |
|---|---|
| Getting Started | Installation and first steps |
| Defining Settings | Complete setting options |
| Custom Types | Add support for your own types |
| Validators | Built-in and custom validation |
| Testing | Mock configuration in unit tests |
| Serializers | Custom serialization formats |
| Thread Safety | Concurrent access with MultiThreadedPolicy |
By default, Configuration uses SingleThreadedPolicy (zero overhead). For concurrent access, opt in to reader-writer locking:
// Thread-safe configuration:
cppfig::Configuration<MySchema, cppfig::ConfSerializer, cppfig::MultiThreadedPolicy>
config("config.conf");| Policy | Overhead | Use case |
|---|---|---|
SingleThreadedPolicy (default) |
None | Single-threaded or externally synchronized |
MultiThreadedPolicy |
std::shared_mutex |
Concurrent reads and writes from multiple threads |
Getacquires a shared (reader) lock — multiple concurrent readers allowed.Set/Loadacquire an exclusive (writer) lock.Save/Diff/ValidateAllacquire a shared lock (read-only).- Validation in
Setruns before the exclusive lock, so invalid values never block readers.
Every setting is a struct with:
struct MySetting {
// Required
static constexpr std::string_view path = "path.to.setting";
using value_type = int;
static auto default_value() -> int { return 42; }
// Optional
static constexpr std::string_view env_override = "MY_SETTING";
static auto validator() -> cppfig::Validator<int> { return cppfig::Min(0); }
};- Environment variable (if
env_overridedefined and env var set) - File value (if present in configuration file)
- Default value (from
default_value())
// Numeric
cppfig::Min(0)
cppfig::Max(100)
cppfig::Range(1, 65535)
cppfig::Positive<int>()
cppfig::NonNegative<int>()
// String
cppfig::NotEmpty()
cppfig::MinLength(8)
cppfig::MaxLength(255)
// Generic
cppfig::OneOf<std::string>({"a", "b", "c"})
cppfig::Predicate<int>([](int x) { return x % 2 == 0; }, "must be even")
// Combine
cppfig::Min(1).And(cppfig::Max(100))struct Point {
int x, y;
};
// Register with cppfig — works with any serializer
template <>
struct cppfig::ConfigTraits<Point> {
static auto Serialize(const Point& p) -> cppfig::Value {
auto obj = cppfig::Value::Object();
obj["x"] = cppfig::Value(p.x);
obj["y"] = cppfig::Value(p.y);
return obj;
}
static auto Deserialize(const cppfig::Value& v) -> std::optional<Point> {
try {
return Point{static_cast<int>(v["x"].Get<int64_t>()),
static_cast<int>(v["y"].Get<int64_t>())};
} catch (...) {
return std::nullopt;
}
}
static auto ToString(const Point& p) -> std::string {
return "(" + std::to_string(p.x) + "," + std::to_string(p.y) + ")";
}
static auto FromString(std::string_view) -> std::optional<Point> {
return std::nullopt;
}
};
// Use in settings
struct Origin {
static constexpr std::string_view path = "origin";
using value_type = Point;
static auto default_value() -> Point { return {0, 0}; }
};If you have the JSON feature enabled and your type already has nlohmann ADL functions, you can use the shortcut:
#include <cppfig/json.h>
template <>
struct cppfig::ConfigTraits<Point> : cppfig::ConfigTraitsFromJsonAdl<Point> {};#include <cppfig/testing/mock.h>
TEST(MyTest, UsesConfiguration) {
cppfig::testing::MockConfiguration<MySchema> config;
config.SetValue<settings::ServerPort>(9000);
MyService service(config.Get<settings::ServerHost>(),
config.Get<settings::ServerPort>());
EXPECT_EQ(service.GetPort(), 9000);
}cppfig::Configuration<Schema,
Serializer = ConfSerializer,
ThreadPolicy = SingleThreadedPolicy>
// Methods
auto Load() -> cppfig::Status;
auto Save() const -> cppfig::Status;
auto Get<Setting>() const -> typename Setting::value_type;
auto Set<Setting>(value) -> cppfig::Status;
auto Diff() const -> ConfigDiff;
auto ValidateAll() const -> cppfig::Status;
auto GetFilePath() const -> std::string_view;
// Thread policies
cppfig::SingleThreadedPolicy // Zero-overhead (default)
cppfig::MultiThreadedPolicy // std::shared_mutex reader-writer locking
// Serializers
cppfig::ConfSerializer // Built-in flat .conf (default)
cppfig::JsonSerializer // Requires CPPFIG_ENABLE_JSONcppfig::ConfigSchema<Settings...>
// Compile-time checks
static constexpr bool has_setting<S>;
static constexpr std::size_t size;
// Runtime utilities
static auto GetPaths() -> std::array<std::string_view, size>;
static void ForEachSetting(auto&& fn);# Debug build (Clang)
cmake --workflow --preset=debug-clang
# Debug build (GCC)
cmake --workflow --preset=debug-gcc
# Release build (Clang)
cmake --workflow --preset=release-clang
# Run tests
ctest --test-dir build/debug/clang --output-on-failure
# Run example
./build/debug/clang/examples/cppfig_example
# Run benchmarks
./build/release/clang/benchmark/cppfig_benchmark# Full workflow: configure, build, test, and generate report
cmake --workflow --preset=coverage
# Generate HTML report (after running tests)
ninja -C build/coverage coverage
# Generate text summary
ninja -C build/coverage coverage-text
# Generate XML report (Cobertura format for CI)
ninja -C build/coverage coverage-xmlCoverage reports are generated in build/coverage/coverage/:
index.html- Detailed HTML report with line-by-line coveragecoverage.xml- Cobertura XML for CI integration
cppfig/
├── src/cppfig/ # Library headers
│ ├── cppfig.h # Main include
│ ├── configuration.h # Configuration class
│ ├── schema.h # ConfigSchema template
│ ├── setting.h # Setting concepts
│ ├── traits.h # Type traits (Serialize/Deserialize)
│ ├── value.h # Core Value type
│ ├── validator.h # Validators
│ ├── serializer.h # Serializer concept
│ ├── conf.h # Built-in .conf serializer
│ ├── json.h # Optional JSON serializer
│ ├── interface.h # Mockable interfaces
│ ├── diff.h # Configuration diff
│ ├── logging.h # Logging utilities
│ ├── thread_policy.h # Thread safety policies
│ └── testing/mock.h # Testing helpers
├── examples/ # Example code
├── test/ # Unit & integration tests
├── benchmark/ # Performance benchmarks
└── docs/ # Documentation
- Follow existing code style (
clang-format) - Run static analysis (
clang-tidy) - Add tests for new features
- Update documentation
MIT License - see LICENSE for details.