我有以下代码,我不明白为什么最后 !has_size<Bar>::value
只有在我定义 Bar
之前没有注释掉完全相同的 static_assert( !has_size<Bar>::value, ...)
时才会计算为真
#include <type_traits>
template <class, class = void> struct has_size : std::false_type {};
template <class T> struct has_size<
T, typename std::enable_if<(sizeof(T) > 0)>::type>
: std::true_type
{};
// expected success
struct Foo {};
static_assert( has_size<Foo>::value, "Foo doesn't have size");
struct Bar; // declare bar
// if we comment out this line, the static_assert below struct Bar{} fails
static_assert( !has_size<Bar>::value, "Bar has a size");
struct Bar {}; // define bar
// why is this true now, but false if I comment out the previous assertion?
static_assert( !has_size<Bar>::value, "Bar has a size");
我想稍后根据 has_size<Bar>
的值做出一些模板决定 . msvc,gcc和clang的行为是相同的 . 我依靠这种行为徘徊在UB土地或其他一些灾难中 . 思考?
2 回答
您可以将类模板实例化视为"cached"或"memoized."更正式地说,类模板每个翻译单元具有a single point of instantiation .
所以当你写:
has_size<Bar>
在#1
实例化 . 这是它唯一的实例化点 . 在#2
,我们没有"redo"那个计算 - 所以它仍然是假的 . 如果我们从一个不同的翻译单元再次这样做,以一种不同的答案,那将是不正确的(不需要诊断),但在这种情况下 - 这是一个结构良好的程序 .当您注释掉
#1
时,现在has_size<Bar>
的实例化点变为#2
. 在程序中的那一点上,Bar
已经完成,所以has_size<Bar>
现在是true_type
......所以静态断言会触发 .c中的每个完整类型
T
都有sizeof(T)>0
或者更简单sizeof(T)
是一个有效的表达式,has_size
正在使用它来检测某些类型是否完全没有并且正在通过SFINAE进行 .第一个
static_assert
导致
has_size<Bar>
的实例化,它在实例化的时候Bar
是不完整的,这导致has_size
的第二个特化中的测试sizeof(T) > 0
失败,这个故障依赖于使用满足has_size<Bar>::value == false
的主模板has_size : std::false_type
的定义 .当第二个
static_assert
在评估时,再次请求特化
has_size<Bar>
,但是这次Bar
已经完成并且已经有has_size<Bar>
(从std::false_type
继承的那个)的实例化,使用该特化而不是实例化新的,因此仍然说has_type<Bar>::value == false
.当您评论第一个
static_assert
(1)时,在评估的时刻(2),Bar
已经定义,现在sizeof(T) > 0
有效且为真,它选择has_size<Bar> : std::true_type
的特化,现在它满足has_type<Bar>::value == true
.没有涉及UB .
为了获得反映
T
类型完整性变化的特征,您可以同意: