`std::tuple_size_v`在不同编译器上的不同SFINAE行为
考虑以下代码:
#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])的常量求值需要该变量或函数,即使不需要该表达式的常量求值,或者常量表达式求值不使用该定义,则认为该变量或函数的定义的存在会影响程序的语义。
但情况并非如此:我们不需要变量来进行常量计算。
相关文章