Reputation: 43
In the following code why does the second and third concepts produce a compilation error?
#include <tuple>
template <class P>
concept IsPair1 = std::tuple_size<P>::value == 2;
template <class P>
concept IsPair2 = std::tuple_size_v<P> == 2;
template <class P>
concept IsPair3 = requires { typename std::tuple_size<P>; } && std::tuple_size_v<P> == 2;
constexpr bool intIsPair1 = IsPair1<int>; // OK, false
constexpr bool intIsPair2 = IsPair2<int>; // error: incomplete type 'std::tuple_size<int>' used in nested name specifier
constexpr bool intIsPair3 = IsPair3<int>; // error: incomplete type 'std::tuple_size<int>' used in nested name specifier
/usr/local/Cellar/gcc/11.1.0_1/include/c++/11.1.0/tuple:1334:61: error: incomplete type 'std::tuple_size<int>' used in nested name specifier
1334 | inline constexpr size_t tuple_size_v = tuple_size<_Tp>::value;
| ^~~~~
I expected all three to be evaluated to false as according to https://en.cppreference.com/w/cpp/language/constraints,
Satisfaction of an atomic constraint is checked by substituting the parameter mapping and template arguments into the expression E. If the substitution results in an invalid type or expression, the constraint is not satisfied.
Upvotes: 2
Views: 387
Reputation: 137315
The initializer of a variable template is not in the immediate context, so any error there causes a hard error instead of a substitution failure.
std::tuple_size<P>::value == 2
works because the attempt to name the member value
of an incomplete type is in the immediate context.
requires { typename std::tuple_size<P>; } && std::tuple_size_v<P> == 2
doesn't work because the first part only checks that std::tuple_size<P>
is a type, which is trivially satisfied since tuple_size
is an unconstrained class template.
Upvotes: 6