In the Ranges spec N4622 the Same
concept is defined to take two types T
and U
, but is sometimes used inside requires
with ju
A quick recap to make sure we’re all on the same page: a placeholder type is almost, but not quite a type. It’s a stand-in for a type that will be effectively be deduced. (Similarly there also are placeholder non-types [resp. templates] which are stand-ins for non-types. To avoid going on a tangent I’ll just mention their existence here and use the all-encompassing placeholder term from now on.)
Before concepts the only placeholders we have are the auto
and decltype(auto)
specifiers:
// the specifiers are all placeholders for `int`
auto i = 42;
auto& lref = i;
auto&& rref = 0;
decltype(auto) = 2 * i;
With concepts we can use placeholders in more intricate ways:
// two placeholders for `int`
std::pair p = std::make_pair(1, 4);
// one placeholder
std::pair q = p;
And here’s where it gets tricky: a concept itself can be used as a placeholder:
template
concept bool Integral = …;
// placeholder for `int` constrained by `Integral`
Integral a = 0;
// same as above
Integral<> b = a;
template
concept bool EqualityComparable = …;
// another placeholder for `int`
// this time constrained by `EqualityComparable`
EqualityComparable c = a;
Read the binary EqualityComparable
example carefully. What makes concepts as placeholders tricky is that the very first concept parameter has special treatment and will not be mentioned in the argument list. Whatever arguments appear in the angle bracket list (if any) correspond to the subsequent parameters.
Let’s write a concept for something that has a size()
. For the sake of demonstration we’ll be expecting that the result of this size()
operation should be usable as an Incrementable
variable (rather than something sensible like an Integral
concept).
template
concept bool Incrementable = requires(Incr incr) {
++incr;
};
template
concept bool Sizeable = requires(Cont const cont) {
// requirement #1
{ cont.size() } -> Incrementable
};
Our requirement #1 is a compound requirement of a special kind. Namely, it is one where a placeholder appears in the syntactic trailing-return-type. The effects are as if we had written:
template
void valid_for_incrementable(Incr incr);
template
concept bool Sizeable = requires(Cont const cont) {
cont.size();
valid_for_incrementable(cont.size());
};
Plainly put the purpose of a compound requirement is to introduce two constraints at once: that the expression in brackets is valid, and that it can be used as the argument to an invented constraint-validating function template.
Armed with our knowledge of placeholders and of their uses in compound requirements, we can find our answer:
template
concept bool Demo = requires(T t) {
{ t } -> C;
};
effectively means that we introduce a C
constraint on the t
expression. Had the placeholder been C
instead, then the constraint would have been C
instead and so on.