When using a type constraint on a forwarding reference argument, the constraint is given as an lvalue reference to the type. For example, the call to h in the following code does not compile because it would require std::integral<int &> to hold, but integral is not true for references (see https://godbolt.org/z/Taeb5Exdv):
#include <concepts>
void f(std::integral auto &i) {}
void g(const std::integral auto &i) {}
void h(std::integral auto &&i) {}
int main() {
int one = 1;
f(one);
g(one);
h(one); // "[...] the expression 'is_integral_v<_Tp> [with _Tp = int&]' evaluated to 'false'"
}
(The error is counter-intuitive to me, because f and g will evaluate integral<int> rather than integral<int&>/integral<const int&>, so my mind unconsciously extrapolated that to a "rule" like "the template arguments have cvref removed". But OK, it's more complicated; forwarding references are different, probably for a reason; I can accept that this is just "the way it works".)
I can work around this by replacing h by e.g. (see https://godbolt.org/z/fa9f7eeq6)
void h1(auto &&i) requires std::integral<std::remove_cvref_t<decltype(i)>> {}
or
template <class T>
void h2(T &&i) requires std::integral<std::remove_cvref_t<T>> {}
or
template <class T>
concept Integral_without_cvref = std::integral<std::remove_cvref_t<T>>;
void h3(Integral_without_cvref auto &&i) {}
All are a bit complicated: h1 and h2 requiring more syntax in the function declarations and h3 requiring a special concept.
Is there a more idiomatic/succinct way to declare constraints on forwarding reference arguments?
void h5(std::integral auto i) {}?namespace unqualified { template <class T> concept Integral = std::integral<std::remove_cvref_t<T>>; }and thenvoid h3(unqualified::Integral auto&&) {}