Note: everything that follows uses the Concepts TS implementation in GCC 6.1
Let's say I have a concept Surface
, like the following:
template <typename T>
concept bool Surface() {
return requires(T& t, point2f p, float radius) {
{ t.move_to(p) };
{ t.line_to(p) };
{ t.arc(p, radius) };
// etc...
};
}
Now I want to define another concept, Drawable
, which matches any type with a member function:
template <typename S>
requires Surface<S>()
void draw(S& surface) const;
i.e.
struct triangle {
void draw(Surface& surface) const;
};
static_assert(Drawable<triangle>(), ""); // Should pass
That is, a Drawable
is something which has a templated const member function draw()
taking an lvalue reference to something which satisfies the Surface
requirements. This is reasonably easy to specify in words, but I can't quite work out how to do it in C++ with the Concepts TS. The "obvious" syntax doesn't work:
template <typename T>
concept bool Drawable() {
return requires(const T& t, Surface& surface) {
{ t.draw(surface) } -> void;
};
}
error: 'auto' parameter not permitted in this context
Adding a second template parameter allows the concept definition to compile, but:
template <typename T, Surface S>
concept bool Drawable() {
return requires(const T& t, S& s) {
{ t.draw(s) };
};
}
static_assert(Drawable<triangle>(), "");
template argument deduction/substitution failed: couldn't deduce template parameter 'S'
now we can only check whether a particular <Drawable
, Surface
> pair matches the Drawable
concept, which isn't quite right. (A type D
either has the required member function or it does not: that doesn't depend on which particular Surface
we check.)
I'm sure it's possible to do what I'm after, but I can't work out the syntax and there aren't too many examples online yet. Does anybody know how to write a concept definition which requires type to have a constrained template member function?
What you're looking for is for a way for the compiler to synthesize an archetype of Surface
. That is, some private, anonymous type that minimally satisfies the Surface
concept. As minimally as possible. Concepts TS doesn't currently allow for a mechanism for automatically synthesizing archetypes, so we're left with doing it manually. It's quite a complicated process, since it's very easy to come up with archetype candidates that have way more functionality that the concept specifies.
In this case, we can come up with something like:
namespace archetypes {
// don't use this in real code!
struct SurfaceModel {
// none of the special members
SurfaceModel() = delete;
SurfaceModel(SurfaceModel const& ) = delete;
SurfaceModel(SurfaceModel&& ) = delete;
~SurfaceModel() = delete;
void operator=(SurfaceModel const& ) = delete;
void operator=(SurfaceModel&& ) = delete;
// here's the actual concept
void move_to(point2f );
void line_to(point2f );
void arc(point2f, float);
// etc.
};
static_assert(Surface<SurfaceModel>());
}
And then:
template <typename T>
concept bool Drawable() {
return requires(const T& t, archetypes::SurfaceModel& surface) {
{ t.draw(surface) } -> void;
};
}
These are valid concepts, that probably work. Note that there's a lot of room for even more refinement on the SurfaceModel
archetype. I have a specific function void move_to(point2f )
, but the concept just requires that it's callable with an lvalue of type point2f
. There's no requirement that move_to()
and line_to()
both take an argument of type point2f
, they could both take complete different things:
struct SurfaceModel {
// ...
struct X { X(point2f ); };
struct Y { Y(point2f ); };
void move_to(X );
void line_to(Y );
// ...
};
This kind of paranoia makes for a better archetype, and serves to illustrate how complex this problem could be.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With