I was reading the cppreference page on Constraints and noticed this example:
// example constraint from the standard library (ranges TS)
template
I'm still very new to concepts, so feel free to point out any errors I need to fix in this answer. The answer is divided into three sections: The first directly regards the use of std::forward
, the second expands on Swappable
, and the third regards the internal error.
This appears to be a typo1, and likely should be requires(T&& t, U&& u)
. In this case, perfect forwarding is used to ensure that the concept will be properly evaluated for both lvalue and rvalue references, guaranteeing that only lvalue references will be marked as swappable.
The full Ranges TS Swappable concept, which this is based on, is fully defined as:
template
concept bool Swappable() {
return requires(T&& a, T&& b) {
ranges::swap(std::forward(a), std::forward(b));
};
}
template
concept bool Swappable() {
return ranges::Swappable() &&
ranges::Swappable() &&
ranges::CommonReference() &&
requires(T&& t, U&& u) {
ranges::swap(std::forward(t), std::forward(u));
ranges::swap(std::forward(u), std::forward(t));
};
}
The concept shown on the Constraints and concepts page is a simplified version of this, which appears to be intended as a minimal implementation of library concept Swappable
. As the full definition specifies requires(T&&, U&&)
, it stands to reason that this simplified version should as well. std::forward
is thus used with the expectation that t
and u
are forwarding references.
1: Cubbi's comment, made while I was testing code, doing research, and eating supper, confirms that it's a typo.
[The following expands on Swappable
. Feel free to skip it if this doesn't concern you.]
Note that this section only applies if Swappable
is defined outside namespace std
; if defined in std
, as it appears to be in the draft, the two std::swap()
s will automatically be considered during overload resolution, meaning no additional work is required to include them. Thanks go to Cubbi for linking to the draft and stating that Swappable
was taken directly from it.
Note, however, that the simplified form by itself isn't a full implementation of Swappable
, unless using std::swap
has already been specified. [swappable.requirements/3] states that overload resolution must consider both the two std::swap()
templates and any swap()
s found by ADL (i.e., resolution must proceed as if the using-declaration using std::swap
had been specified). As concepts cannot contain using-declarations, a more complete Swappable
might look something like this:
template
concept bool ADLSwappable = requires(T&& t, U&& u) {
swap(std::forward(t), std::forward(u));
swap(std::forward(u), std::forward(t));
};
template
concept bool StdSwappable = requires(T&& t, U&& u) {
std::swap(std::forward(t), std::forward(u));
std::swap(std::forward(u), std::forward(t));
};
template
concept bool Swappable = ADLSwappable || StdSwappable;
This expanded Swappable
will allow for proper detection of parameters that fulfil the library concept, like so.
[The following regards GCC's internal error, and isn't directly related to Swappable
itself. Feel free to skip it if this doesn't concern you.]
To use this, however, f()
needs a few modifications. Rather than:
void f(Swappable& x) {}
One of the following should instead be used:
template
void f(T&& x) requires Swappable {}
template
void f(T& x) requires Swappable {}
This is due to an interaction between GCC and concept resolution rules, and will probably be sorted out in future versions of the compiler. Using a constraint-expression sidesteps the interaction that I believe is responsible for the internal error, making it a viable (if more verbose) stopgap measure for the time being.
The internal error appears to be caused by the way GCC handles concept resolution rules. When it encounters this function:
void f(Swappable& x) {}
As function concepts can be overloaded, concept resolution is performed when concept names are encountered in certain contexts (such as when used as a constrained type specifier, like Swappable
is here). Thus, GCC attempts to resolve Swappable
as specified by concept resolution rule #1, in the Concept resolution section of this page:
As Swappable
is used without a parameter list, it takes a single wildcard as its argument. This wildcard can match any possible template parameter (whether type, non-type, or template), and thus is a perfect match for T
.
As Swappable
's second parameter doesn't correspond to an argument, its default template argument will be used, as specified after the numbered rules; I believe this to be the problem. As T
is currently (wildcard)
, a simplistic approach would be to temporarily instantiate U
as either another wildcard or a copy of the first wildcard, and determine whether Swappable<(wildcard), (wildcard)>
matches the pattern template
(it does); it could then deduce T
, and use that to properly determine whether it resolves to the Swappable
concept.
Instead, GCC appears to have reached a Catch-22: It can't instantiate U
until it deduces T
, but it can't deduce T
until it determines whether this Swappable
correctly resolves to the Swappable
concept... which it can't do without U
. So, it needs to figure out what U
is before it can figure out whether we have the right Swappable
, but it needs to know whether we have the right Swappable
before it can figure out what U
is; faced with this unresolvable conundrum, it has an aneurysm, keels over, and dies.