Overview
Apply C++20 Concepts to common_system to strengthen compile-time type validation, provide clearer error messages, and improve API usability.
Background
Currently, common_system is written based on C++17 and uses SFINAE for template metaprogramming. Introducing C++20 Concepts provides:
- Improved compile error readability: Template errors displayed as clear concept violation messages instead of hundreds of lines
- API documentation: Concepts serve as self-documentation, clearly expressing type requirements
- Code simplification: Eliminates
std::enable_if boilerplate
- Better IDE support: More accurate auto-completion and type hints
Proposed Concepts
1. Result/Optional Related Concepts
namespace kcenon::common::concepts {
/// A type that can contain either a value or an error
template<typename T>
concept Resultable = requires(T t) {
{ t.is_ok() } -> std::convertible_to<bool>;
{ t.is_err() } -> std::convertible_to<bool>;
};
/// A type that supports unwrapping (value extraction)
template<typename T>
concept Unwrappable = requires(T t) {
{ t.unwrap() } -> std::same_as<typename T::value_type&>;
{ t.unwrap_or(std::declval<typename T::value_type>()) }
-> std::same_as<typename T::value_type>;
};
/// A type that supports monadic operations
template<typename T>
concept Mappable = requires(T t) {
{ t.map(std::declval<std::function<int(typename T::value_type)>>()) };
{ t.and_then(std::declval<std::function<T(typename T::value_type)>>()) };
};
} // namespace concepts
2. Executor Related Concepts
namespace kcenon::common::concepts {
/// A callable type
template<typename F, typename... Args>
concept Invocable = std::invocable<F, Args...>;
/// A callable type that returns void
template<typename F, typename... Args>
concept VoidCallable = Invocable<F, Args...> &&
std::is_void_v<std::invoke_result_t<F, Args...>>;
/// A type that satisfies the Job interface
template<typename T>
concept JobLike = requires(T t) {
{ t.execute() } -> std::same_as<VoidResult>;
{ t.get_name() } -> std::convertible_to<std::string>;
{ t.get_priority() } -> std::convertible_to<int>;
};
/// A type that satisfies the Executor interface
template<typename T>
concept ExecutorLike = requires(T t) {
{ t.worker_count() } -> std::convertible_to<size_t>;
{ t.is_running() } -> std::convertible_to<bool>;
{ t.pending_tasks() } -> std::convertible_to<size_t>;
{ t.shutdown(true) } -> std::same_as<void>;
};
} // namespace concepts
3. Event Bus Related Concepts
namespace kcenon::common::concepts {
/// A type that can be used as an event
template<typename T>
concept EventType = std::is_class_v<T> &&
std::is_copy_constructible_v<T>;
/// A type that can be used as an event handler
template<typename H, typename E>
concept EventHandler = requires(H h, const E& e) {
{ h(e) } -> std::same_as<void>;
};
/// A type that can be used as an event filter
template<typename F, typename E>
concept EventFilter = requires(F f, const E& e) {
{ f(e) } -> std::convertible_to<bool>;
};
} // namespace concepts
4. Service Container Related Concepts
namespace kcenon::common::concepts {
/// A type that can be used as a service interface
template<typename T>
concept ServiceInterface = std::is_polymorphic_v<T> &&
std::has_virtual_destructor_v<T>;
/// A type that can be used as a service implementation
template<typename TImpl, typename TInterface>
concept ServiceImplementation =
ServiceInterface<TInterface> &&
std::is_base_of_v<TInterface, TImpl> &&
std::is_constructible_v<TImpl>;
/// A type that can be used as a factory function
template<typename F, typename T>
concept ServiceFactory = requires(F f, IServiceContainer& c) {
{ f(c) } -> std::convertible_to<std::shared_ptr<T>>;
};
} // namespace concepts
5. Configuration Related Concepts
namespace kcenon::common::concepts {
/// A serializable configuration type
template<typename T>
concept ConfigSection =
std::is_default_constructible_v<T> &&
std::is_copy_constructible_v<T>;
/// A validatable type
template<typename T>
concept Validatable = requires(T t) {
{ t.validate() } -> std::same_as<VoidResult>;
};
} // namespace concepts
Application Examples
Before (SFINAE-based)
template<typename F,
typename = std::enable_if_t<
std::is_invocable_v<F> &&
std::is_void_v<std::invoke_result_t<F>>>>
void execute_async(F&& func);
After (Concepts-based)
template<VoidCallable F>
void execute_async(F&& func);
Error Message Comparison
Before (SFINAE):
error: no matching function for call to 'execute_async'
note: candidate template ignored: substitution failure [with F = int]:
no type named 'type' in 'std::enable_if<false>'
After (Concepts):
error: constraints not satisfied for 'execute_async' [with F = int]
note: because 'int' does not satisfy 'VoidCallable'
note: because 'std::invocable<int>' evaluated to false
Implementation Plan
Phase 1: Define Base Concepts
Phase 2: Refactor Existing Code
Phase 3: Improve Service Container
Phase 4: Documentation and Testing
Compatibility Considerations
Conditional Compilation
#if __cplusplus >= 202002L && defined(__cpp_concepts)
#define COMMON_HAS_CONCEPTS 1
#else
#define COMMON_HAS_CONCEPTS 0
#endif
#if COMMON_HAS_CONCEPTS
template<concepts::VoidCallable F>
void execute_async(F&& func);
#else
template<typename F,
typename = std::enable_if_t<std::is_invocable_v<F>>>
void execute_async(F&& func);
#endif
Supported Compilers
- GCC 10+ (full concepts support)
- Clang 10+ (full concepts support)
- MSVC 2019 16.3+ (partial), 2022+ (full)
Expected Benefits
| Item |
Improvement |
| Compile error readability |
80% reduction in template error messages |
| Lines of code |
~30% reduction in SFINAE boilerplate |
| IDE support |
Improved auto-completion accuracy |
| API documentation |
Explicit expression of type requirements |
| Maintainability |
Centralized type constraints |
Proposed File Structure
include/kcenon/common/
├── concepts/
│ ├── concepts.h # Unified header for all concepts
│ ├── core.h # Basic concepts (Resultable, Unwrappable)
│ ├── callable.h # Callable-related concepts
│ ├── event.h # Event-related concepts
│ └── service.h # DI-related concepts
├── interfaces/
│ └── ... # Existing interfaces (with concepts applied)
└── patterns/
└── ... # Existing patterns (with concepts applied)
Related Links
Overview
Apply C++20 Concepts to
common_systemto strengthen compile-time type validation, provide clearer error messages, and improve API usability.Background
Currently,
common_systemis written based on C++17 and uses SFINAE for template metaprogramming. Introducing C++20 Concepts provides:std::enable_ifboilerplateProposed Concepts
1. Result/Optional Related Concepts
2. Executor Related Concepts
3. Event Bus Related Concepts
4. Service Container Related Concepts
5. Configuration Related Concepts
Application Examples
Before (SFINAE-based)
After (Concepts-based)
Error Message Comparison
Before (SFINAE):
After (Concepts):
Implementation Plan
Phase 1: Define Base Concepts
concepts/core.h: Basic type conceptsconcepts/callable.h: Callable type conceptsconcepts/container.h: Container-related conceptsPhase 2: Refactor Existing Code
Result<T>template parametersIExecutormethodssimple_event_bustemplatesPhase 3: Improve Service Container
ServiceInterfaceconcept toregister_factory<T>ServiceImplementationconcept toregister_type<TImpl, TIface>Phase 4: Documentation and Testing
Compatibility Considerations
Conditional Compilation
Supported Compilers
Expected Benefits
Proposed File Structure
Related Links