Writing Your Own Concepts¶
Starting Point: Good Old Function¶
N-dimensional hypotenuse
std::vector<double>
Using index based iteration, only too keep requirements to a minimum
Requirements
.size()
.operator[]()
double hypotenuse(const std::vector<double>& v)
{
double sumsq = 0;
for (size_t i=0; i<v.size(); ++i) // <--- vector has .size()
sumsq += v[i]*v[i]; // <--- vector has []
return std::sqrt(sumsq);
}
Need Template¶
point2d
: membersx, y
(and potentially some operations on a point, but that is not the point here)How to fit that into
hypotenuse()
?⟶ implement what’s required by
hypotenuse()
(.size()
and.operator[]()
)⟶ Turn
hypotenuse()
into a templateStraightforward
class point2d
{
public:
point2d(double x, double y)
: _x{x}, _y{y} {}
size_t size() const { return 2; } // <--- point2d has .size() too
double operator[](size_t i) const // <--- point2d has [] too
{
if (i==0) return _x;
if (i==1) return _y;
return 666;
}
private:
double _x, _y;
};
Error: Requirement Not Fulfilled¶
class point2d
{
// size_t size() const { return 2; } // <--- requirement *not* fulfilled
};
Uncomment
.size()
Error message is relatively clear (see below)
error: ‘const class point2d’ has no member named ‘size’
Can be worse though; for example if helper types (possibly nested a dozen levels deep) are instantiated by the implementation
example-3-requirement-not-fulfilled.cpp: In instantiation of ‘double hypotenuse(const V&) [with V = point2d]’:
example-3-requirement-not-fulfilled.cpp:39:28: required from here
example-3-requirement-not-fulfilled.cpp:9:26: error: ‘const class point2d’ has no member named ‘size’
9 | for (size_t i=0; i<v.size(); ++i)
| ~~^~~~
Concept: has_size
¶
Implement concept
has_size
Requires that any object of
V
has a.size()
method⟶ i.e. the expression
v.size()
compiles
template <typename V>
concept has_size = requires(V v) {
v.size(); // <--- compiles
};
Concept usage: good old full explicit template
template <typename V>
requires has_size<V>
double hypotenuse(const V& v) { /*...*/ }
Concept usage: good old full explicit template (after declaration)
template <typename V>
double hypotenuse(const V& v) requires has_size<V> { /*...*/ }
Concept usage: abbreviated function templates
double hypotenuse(const has_size auto& v) { /*...*/ }
Concept: index_returns_double
¶
Hmm … what if elements are not
double
?⟶ Somebody could use
hypotenuse()
on something that hasint
coordinatesCould we check this?
Ruin the whole thing …
Modify object initialization to take real double values, e.g.
{3.5L, 4.5L}
⟶ Result not straight 5 anymore
Modify
point2d::operator[]()
to returnint
⟶ Result straight 5 again
Concept index_returns_double
First use
std::same_as<double>
⟶ Constraint check fails:
std::vector::operator[]()
is not same asdouble
(rather, it returnsdouble&
)Solution:
std::commone_reference_with<double>
template <typename V>
concept index_returns_double = requires(V v) {
{ v[0] } -> std::common_reference_with<double>;
};
Argh: checking multiple constraints is not possible with abbreviated function templates
⟶ fall back to ordinary template syntax, and its
requires
clause