Constrained forwarding references considered sketchy as frick
I’ll just leave this here.
template<class I> requires is_integral_v<I>
void foo(I&& i) {
puts("ONE");
}
template<class T> // unconstrained
void foo(T&& t) {
puts("TWO");
}
The intent of this code (says the casual reader) is that foo
takes
one parameter by forwarding reference; and if that parameter is integral,
it’ll do one thing and otherwise it’ll do the other thing. But watch:
int main() {
constexpr int zero = 0;
foo(int(zero));
foo(zero);
}
This prints “ONE TWO”.
This just came up in the wild, so to speak, on the Code Review StackExchange.
UPDATE, 2023-08-13: This remains true for arbitrary constraints and concepts
like std::integral
. But some C++20 concepts are meant to be used this way;
notably, C++20 Ranges expects you to write
void foo(std::ranges::range auto&& rg) {
use(rg);
}
The trick in Ranges’ case is that the concept is specially designed to ignore
ref-qualification and Do The Right Thing with const-qualification; in Ranges’
case this is part of a constellation of decisions, such as that taking by
const range auto& rg
would be wrong
and that you are going to treat rg
as an lvalue instead of perfect-forwarding it along.