Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different SFINAE behavior of `std::tuple_size_v` on different compilers

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.

like image 283
HolyBlackCat Avatar asked Feb 15 '21 19:02

HolyBlackCat


1 Answers

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.

like image 120
Barry Avatar answered Nov 08 '22 11:11

Barry