Summary
Adopt C++20 Concepts to replace abstract class-based interfaces and provide compile-time type constraints for template functions. This modernization will improve type safety, provide better error messages, and enable more expressive API design.
5W1H Specification
- Who: common_system maintainers
- What: Introduce C++20 concepts for interface definitions and template constraints
- Where:
include/kcenon/common/interfaces/, include/kcenon/common/patterns/
- When: v0.4.0.0 (non-breaking enhancement)
- Why:
- Current interfaces use abstract classes requiring runtime dispatch
- Template functions lack explicit type constraints
- Error messages for template misuse are cryptic
- C++20 concepts provide compile-time duck typing with clear constraints
- How: Define concepts alongside existing interfaces, migrate gradually
Priority
MEDIUM - Enhancement that improves developer experience without breaking existing code.
Current State Analysis
Existing Abstract Interfaces
// Current approach: abstract class with virtual methods
class IExecutor {
public:
virtual ~IExecutor() = default;
virtual void execute(std::function<void()> task) = 0;
virtual void shutdown() = 0;
virtual bool is_running() const = 0;
};
// Consumers must inherit and implement
class thread_pool_executor : public IExecutor {
void execute(std::function<void()> task) override { /*...*/ }
void shutdown() override { /*...*/ }
bool is_running() const override { /*...*/ }
};
Template Functions Without Constraints
// Current: No constraints, cryptic errors if T doesn't have expected methods
template<typename T>
Result<T> map(std::function<T(U)> func);
// Current: Macros for conditional behavior
#define COMMON_RETURN_IF_ERROR(result) \
if (!(result).is_ok()) return (result).error()
Proposed Solution
Phase 1: Define Core Concepts
// include/kcenon/common/concepts/executor.h
namespace kcenon::common::concepts {
template<typename E>
concept Executor = requires(E executor, std::function<void()> task) {
{ executor.execute(task) } -> std::same_as<void>;
{ executor.shutdown() } -> std::same_as<void>;
{ executor.is_running() } -> std::convertible_to<bool>;
};
template<typename J>
concept Job = requires(J job) {
{ job.execute() } -> std::same_as<void>;
{ job.get_name() } -> std::convertible_to<std::string_view>;
{ job.get_priority() } -> std::convertible_to<int>;
};
template<typename L>
concept Logger = requires(L logger, std::string_view msg, log_level level) {
{ logger.log(level, msg) } -> std::same_as<void>;
{ logger.is_enabled(level) } -> std::convertible_to<bool>;
};
template<typename M>
concept MetricCollector = requires(M collector, std::string_view name, double value) {
{ collector.increment(name, value) } -> std::same_as<void>;
{ collector.gauge(name, value) } -> std::same_as<void>;
{ collector.histogram(name, value) } -> std::same_as<void>;
};
} // namespace kcenon::common::concepts
Phase 2: Constrained Template Functions
// include/kcenon/common/patterns/result.h
namespace kcenon::common {
template<typename T>
concept ResultValue = std::movable<T> || std::is_void_v<T>;
template<typename F, typename T>
concept ResultMapper = requires(F f, T value) {
{ std::invoke(f, std::move(value)) };
};
template<ResultValue T>
class Result {
public:
template<ResultMapper<T> F>
auto map(F&& func) -> Result<std::invoke_result_t<F, T>>;
template<typename F>
requires std::invocable<F, T> &&
std::same_as<std::invoke_result_t<F, T>, Result<typename std::invoke_result_t<F, T>::value_type>>
auto and_then(F&& func);
};
} // namespace kcenon::common
Phase 3: Concept-Based Function Overloads
// Type-safe logging that works with any Logger implementation
template<concepts::Logger L>
void log_error(L& logger, std::string_view message) {
logger.log(log_level::error, message);
}
// Executor-agnostic task submission
template<concepts::Executor E>
void submit_task(E& executor, std::function<void()> task) {
executor.execute(std::move(task));
}
Proposed Concepts List
Core Concepts
| Concept |
Purpose |
Replaces |
Executor |
Task execution interface |
IExecutor |
Job |
Executable job unit |
IJob |
Logger |
Logging interface |
ILogger |
MetricCollector |
Metrics collection |
IMetricCollector |
Monitor |
Health monitoring |
IMonitor |
HttpClient |
HTTP client interface |
IHttpClient |
UdpClient |
UDP client interface |
IUdpClient |
Result-Related Concepts
| Concept |
Purpose |
ResultValue |
Valid types for Result |
ResultMapper |
Functions that can be passed to map() |
ResultBinder |
Functions for and_then() |
ErrorHandler |
Functions for or_else() |
Container-Related Concepts (for container_system)
| Concept |
Purpose |
Serializable |
Types that can be serialized |
ContainerValue |
Valid container value types |
Migration Strategy
Phase 1: Parallel Existence (Non-breaking)
// Both approaches work simultaneously
class IExecutor { /*...*/ }; // Keep existing
template<typename E>
concept Executor = requires(E e) { /*...*/ }; // Add concept
// IExecutor implementations automatically satisfy Executor concept
static_assert(Executor<thread_pool_executor>);
Phase 2: Prefer Concepts in New Code
// New APIs use concepts by default
template<Executor E>
class task_scheduler { /*...*/ };
// Existing APIs remain unchanged for compatibility
void legacy_function(IExecutor& executor);
Phase 3: Deprecation (v0.5.0+)
// Mark abstract classes as deprecated
class [[deprecated("Use Executor concept instead")]] IExecutor { /*...*/ };
Tasks
Phase 1: Core Concepts
Phase 2: Result Concepts
Phase 3: Documentation
Phase 4: Downstream Integration
Acceptance Criteria
Benefits
| Aspect |
Before (Abstract Classes) |
After (Concepts) |
| Error Messages |
Deep template instantiation stack |
Clear constraint violation |
| Runtime Overhead |
Virtual dispatch (~5-10ns) |
Zero (static dispatch) |
| Flexibility |
Requires inheritance |
Duck typing |
| Documentation |
Separate from code |
Embedded in concept definition |
Example Error Message Improvement
Before (Abstract Class)
error: no matching function for call to 'execute'
note: candidate function not viable: no known conversion from
'my_bad_executor' to 'IExecutor&' for 1st argument
After (Concepts)
error: constraints not satisfied for 'Executor<my_bad_executor>'
note: the expression 'executor.is_running()' is invalid
because 'my_bad_executor' has no member named 'is_running'
Related Issues
- kcenon/thread_system: Will benefit from Executor concept
- kcenon/logger_system: Will benefit from Logger concept
- kcenon/monitoring_system: Will benefit from MetricCollector concept
References
Summary
Adopt C++20 Concepts to replace abstract class-based interfaces and provide compile-time type constraints for template functions. This modernization will improve type safety, provide better error messages, and enable more expressive API design.
5W1H Specification
include/kcenon/common/interfaces/,include/kcenon/common/patterns/Priority
MEDIUM - Enhancement that improves developer experience without breaking existing code.
Current State Analysis
Existing Abstract Interfaces
Template Functions Without Constraints
Proposed Solution
Phase 1: Define Core Concepts
Phase 2: Constrained Template Functions
Phase 3: Concept-Based Function Overloads
Proposed Concepts List
Core Concepts
ExecutorIExecutorJobIJobLoggerILoggerMetricCollectorIMetricCollectorMonitorIMonitorHttpClientIHttpClientUdpClientIUdpClientResult-Related Concepts
ResultValueResultMapperResultBinderErrorHandlerContainer-Related Concepts (for container_system)
SerializableContainerValueMigration Strategy
Phase 1: Parallel Existence (Non-breaking)
Phase 2: Prefer Concepts in New Code
Phase 3: Deprecation (v0.5.0+)
Tasks
Phase 1: Core Concepts
include/kcenon/common/concepts/directoryexecutor.hwith Executor, Job conceptslogger.hwith Logger conceptmonitoring.hwith MetricCollector, Monitor conceptstransport.hwith HttpClient, UdpClient conceptsconcepts.hPhase 2: Result Concepts
ResultValueconcept to result.hResultMapperconcept with requires clauseResultBinderconcept for and_thenPhase 3: Documentation
Phase 4: Downstream Integration
Acceptance Criteria
Benefits
Example Error Message Improvement
Before (Abstract Class)
After (Concepts)
Related Issues
References