Seriy
Seriy

Reputation: 43

Why does GCC succeeds on a constraint with std::tuple_size::value, but fails on one with std::tuple_size_v?

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

Answers (1)

T.C.
T.C.

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

Related Questions