Home > Net >  C Concept: How to define this circular-dependent constraint?
C Concept: How to define this circular-dependent constraint?

Time:10-08

I have two classes Communication and Handler. Communication is templated with the Handler it will use.

template<HandlerLike THandler>
struct Communication;

where HandlerLike is a Handler that can be constructed with this of the Communication. i.e., for a satisfied Handler, the constructor Handler(Communication<Handler>* parent) should be available.

Since HandlerLike was used by Communication and also referencing Communication, it is defined circularly.

I tried to use type trait to split the concept definition and implementation using the following code. But it did not compile due to unsatisfied constraint. I wonder why this did not work and how to correct it. Thank you!

#include <concepts>

// Forward declaration of handler type trait
template<typename THandler>
struct is_handler_like;

template<typename THandler>
concept HandlerLike = is_handler_like<THandler>::value;

/// The pointer to the communication object will be stored in the handlers for future reference.
template<HandlerLike THandler>
struct Communication {};


template<typename THandler>
constexpr bool value = requires(
        THandler obj,
        Communication<THandler> *parent
) {
    // THandler should be constructed with the parent pointer.
    { THandler(parent) };
};

// Deferred implementation of is_handler_like
template<typename THandler>
struct is_handler_like : public std::bool_constant<value> {
};


// A dummy handler implementation. 
struct DummyHandler {
    Communication<DummyHandler> *parent{};

    // The constructor should have satisfied the constraint, but the compiler says no. 
    explicit DummyHandler(Communication<DummyHandler> *parent) : parent(parent) {}
};

int main() {
    Communication<DummyHandler> comm;
    return 0;
}

CodePudding user response:

A concept cannot constrain a definition that it references. That is semantically nonsensical.

When you encounter problems like this, it's best to take a step back and ask why the circular dependency exists. Evaluate whether it is truly necessary for Handler to be intimately related to Communication and vice-versa. And then evaluate whether there is something that can be pulled out of these two types into its own thing, upon which the remainder of the other two can depend but is not itself dependent on them.

This is a design problem, not a language problem.

CodePudding user response:

Since we're talking about c 20:

//common concept
template <typename H>
concept HandlerLike = requires(H handler) {
    { handler.read()  };
    { handler.write() };
};

template <typename H, typename C>
concept Handler = 
(HandlerLike<H> && HandlerLike<C>) //H and C are HandlerLike
&& requires (H handler, C *comm) {
    { handler.setCommunication(comm) }; //requires the handler to set the communication obj
};

template <typename C, typename H>
concept Communication = 
(HandlerLike<C> && Handler<H,C>) //C is HandlerLike and H is Handler
&& requires (C comm, H *handler) {
    { C(handler) }; // requires handler constructor
};

Afterwards, implement your classes, so that they match the specific concepts.

  • Related