Skip to content

🚀 Enhancement: Apply C++20 Concepts for Improved API #192

Description

@kcenon

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

  • concepts/core.h: Basic type concepts
  • concepts/callable.h: Callable type concepts
  • concepts/container.h: Container-related concepts

Phase 2: Refactor Existing Code

  • Add constraints to Result<T> template parameters
  • Apply concepts to IExecutor methods
  • Apply concepts to simple_event_bus templates

Phase 3: Improve Service Container

  • Apply ServiceInterface concept to register_factory<T>
  • Apply ServiceImplementation concept to register_type<TImpl, TIface>

Phase 4: Documentation and Testing

  • Write concepts usage guide documentation
  • Add concept violation test cases
  • Update example code

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions