Consider this code:
#include <tuple>
#include <type_traits>
#include <iostream>
template <typename T, typename = void> struct is_tuple_like : std::false_type {};
template <typename T> struct is_tuple_like<T, decltype(std::tuple_size_v<T>, void())> : std::true_type {};
int main()
{
std::cout << is_tuple_like<std::string>::value << '\n';
}
Run on gcc.godbolt.org
On GCC 10.2 and MSVC 19.28 it causes a hard error, along the lines of:
error: incomplete type 'std::tuple_size<...>' used in nested name specifier
On Clang 11.0.1, on the other hand, it compiles and prints 1
, with both libstdc++ and libc++.
Which compiler is correct here?
Notice that Clang prints 1
and not 0
, meaning it doesn't treat std::tuple_size<std::string>::value
(the initializer of tuple_size_v
) as a soft error, but instead chooses to disregard it completely!
Which makes sense in a way, since if tuple_size_v
is defined as template <typename T> inline constexpr size_t tuple_size_v = ...
, the type decltype(tuple_size_v<...>)
doesn't depend on the template parameter, and is always size_t
.
I guess the question boils down to whether the initializer of tuple_size_v
is required to be instantiated here, even though it's not strictly necessary.
I know that I can fix it by replacing std::tuple_size_v<...>
with std::tuple_size<...>::value
, then it prints 0
on all three compilers.
I think Clang has this right.
The rule from [temp.inst]/7 is:
Unless a variable template specialization is a declared specialization, the variable template specialization is implicitly instantiated when it is referenced in a context that requires a variable definition to exist or if the existence of the definition affects the semantics of the program.
This program doesn't require the definition of std::tuple_size_v<std::string>
, only the declaration. And the declaration:
template <typename T>
inline constexpr size_t tuple_size_v = tuple_size<T>::value;
Is enough to evaluate the expression in the partial specialization. decltype(std::tuple_size_v<T>, void())
doesn't depend on the value here at all, for any size_t
, that's a valid expression of type void
.
Were we dealing with a function template instead of a variable template:
template <typename T>
constexpr size_t tuple_size_v() { return tuple_size<T>::value; }
It's perhaps more clear that we don't need the definition, just the declaration, and both gcc and msvc accept this alternate formulation (indeed, gcc even warns on it being pointless): example.
Later, in [temp.inst]/8, we have:
The existence of a definition of a variable or function is considered to affect the semantics of the program if the variable or function is needed for constant evaluation by an expression ([expr.const]), even if constant evaluation of the expression is not required or if constant expression evaluation does not use the definition.
But that is not the case here: we don't need the variable for constant evaluation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With