I'm struggling a lot to understand how concepts and contraints works. Until now I always managed to avoid them with type traits and static_assert
or std::enable_if
(or even SFINAE) but I want to reconcile with c++20 (well, for that part at least, since I have the same understanding struggles for almost anything that was added with c++20).
I have a function with a variadic template parameter for which I want to accept only integral values that are above a threshold, let's say 2
.
For that purpose I defined an integral
concept, and then I add a requires
clause to add the threshold constraint, which gives me this:
template <typename T>
concept integral = std::is_integral<T>::value;
template <integral ... Ts>
void f(Ts ... ts) requires (... && (ts > 2))
{
//blablabla
}
This compiles fine. But when I try to call f()
with argumentsn for example f(8, 6);
, I always get a compile-time error (GCC): error: 'ts#0' is not a constant expression
The full error trace (GCC):
<source>: In substitution of 'template<class ... Ts> requires (... && integral<Ts>) void f(Ts >...) requires (... && ts > 2) [with Ts = {int, int}]': <source>:15:6: required from here <source>:8:6: required by the constraints of 'template<class ... Ts> requires (... && integral<Ts>) void f(Ts ...) requires (... && ts > 2)' <source>:8:43: error: 'ts#0' is not a constant expression 8 | void f(Ts ... ts) requires (... && (ts > 2)) | ~~~~~~~~~~~~~~~^~ <source>: In function 'int main()': <source>:15:6: error: no matching function for call to 'f(int, int)' 15 | f(8, 6); | ~^~~~~~ <source>:8:6: note: candidate: 'template<class ... Ts> requires (... && integral<Ts>) void f(Ts >...) requires (... && f::ts > 2)' 8 | void f(Ts ... ts) requires (... && (ts > 2)) | ^ <source>:8:6: note: substitution of deduced template arguments resulted in errors seen above
What I don't understand is why are the arguments required to be constant expressions, and why is 8
not considered as such ?