I'm writing a template class that wraps a user-supplied class in an elaborate wrapper. I'm looking for a better way to enforce interface requirements for a target type using C++20 concepts.
(The precise application is a template class that simplifies marshalling of a co-await call onto a separate non-coroutine execution context and back again onto a coroutine thread of execution. Think asynchronous i/o wrappers if you must. Consider the non-trivial state machine required to do thread-safe asynchronous time-outs, completion callbacks, teardown, and dispatching back to the original coroutine thread of execution without leaking.)
template <typename T>
concept IoCoServiceImplementation = requires(
T t,
typename T::return_type x,
CoServiceCallback<typename T::return_type> *callback)
{
{t.Execute(callback)} -> std::same_as<void>;
{t.Cancel(callback)} -> std::same_as<bool>;
};
template <IoCoServiceImplementation SERVICE_IMPLEMENTATION>
class CoServiceAwaitable {
...
};
What I want concisely: IoCoServiceImplementation
must provide two methods:
class AnImplementation {
public:
using return_type = RETURN_TYPE;
void Execute(CoServiceCallback<return_type>*);
bool Cancel(CoServiceCallback<return_type>*);
}
Note the significant difference in readability of the concept and the example. Also worth noting is that the generated error message doesn't supply the actual types required. You would have to browse the source of the concept declaration to figure out what callback means when you get error blahblah requires {t.Execute(callback)}
.
While I can think of a dozen ways to hack a concept that requires the methods I want, none of them seem sufficiently readable and self-documenting. And the more I hack, the less readable the declarations, and error messages get.
I'm thinking about exploring whether std::function<>
can be hacked to produce a readable and self-documenting declarations. Something like:
{ std::function< void T::Execute(CoServiceCallback<return_type>*) > };
{ std::function< bool T::Cancel(CoServiceCallback<return_type>*) > };
(which doesn't actually work, obvsly).
Is there a better way to do this? Has anyone delivered style guidelines for concepts yet?
--
Proactively, because I'm sure this will come up...
You could quibble about whether it should be std::same_as<bool>
or std::convertable_to<bool>
. Here's my thinking on that. I expect a bool. If that method returns anything other than bool, I can't imagine what it would it be. So it's reasonable to insist that it not be something I don't expect.
A frequent argument defending the deficiencies of the current C++ concept implementation: that concepts are supposed to enforce what you can do, not the precise implementation. In this case, I don't really care what you can do; I'm concerned with letting you know what you do do.
You can use two requires
clauses, first determine whether T
has a member type of return_type
, which can be used to stop the concept check as early as possible
template<typename T>
concept IoCoServiceImplementation =
requires { typename T::return_type; } &&
requires (
T& t, CoServiceCallback<typename T::return_type>* callback) {
{ t.Execute(callback) } -> std::same_as<void>;
{ t.Cancel(callback) } -> std::same_as<bool>;
};
I'm thinking about exploring whether std::function<> can be hacked to produce a readable and self-documenting declarations.
This is not a good idea, first of all, you need to include extra (and unnecessary) headers, and it's not as intuitive as compound requirements with return-type-requirement.