`std::tuple_size_v`在不同编译器上的不同SFINAE行为

2022-05-16 00:00:00 language-lawyer c++ c++20

考虑以下代码:

#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 << '
';
}

Run on gcc.godbolt.org

在GCC 10.2和MSVC19.28上,它会导致硬错误,大致如下:

error: incomplete type 'std::tuple_size<...>' used in nested name specifier

另一方面,在Clang 11.0.1上,它使用libstdc++和libc++编译和打印1

此处哪个编译器正确?

请注意,Clang打印1而不是0,这意味着它不会将std::tuple_size<std::string>::value(tuple_size_v的初始值设定项)视为软错误,而是选择完全忽略它!

这在某种程度上是有道理的,因为如果tuple_size_v定义为template <typename T> inline constexpr size_t tuple_size_v = ...,则decltype(tuple_size_v<...>)类型不依赖于模板参数,始终为size_t

我想问题可以归结为是否需要在此处实例化tuple_size_v的初始值设定项,即使它不是严格必需的。


我知道我可以通过将std::tuple_size_v<...>替换为std::tuple_size<...>::value来修复它,然后它会在所有三个编译器上打印0


解决方案

我认为Lang有这个权利。

[temp.inst]/7中的规则为:

除非变量模板专门化是声明的专门化,否则在需要存在变量定义的上下文中引用变量模板专门化时,或者如果该定义的存在影响程序的语义,则会隐式实例化该变量模板专门化。

此程序不需要std::tuple_size_v<std::string>的定义,只需要声明。和声明:

template <typename T>
inline constexpr size_t tuple_size_v = tuple_size<T>::value;

足以计算部分专门化中的表达式。decltype(std::tuple_size_v<T>, void())根本不依赖于此处的值,对于任何size_t,这都是void类型的有效表达式。

我们处理的是函数模板而不是变量模板:

template <typename T>
constexpr size_t tuple_size_v() { return tuple_size<T>::value; }

可能更清楚的是,我们不需要定义,只需要声明,GCC和MSVC都接受这种替代提法(事实上,GCC甚至警告它没有意义):example。

稍后,在[temp.inst]/8中,我们有:

如果某个表达式([expr.const])的常量求值需要该变量或函数,即使不需要该表达式的常量求值,或者常量表达式求值不使用该定义,则认为该变量或函数的定义的存在会影响程序的语义。

但情况并非如此:我们不需要变量来进行常量计算。

相关文章