我有以下代码,并且我不理解为什么只有在定义!has_size<Bar>::value
之前我没有注释掉完全相同的static_assert( !has_size<Bar>::value, ...)
时,最后一个Bar
才为true。 / p>
#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国或其他灾难。有想法吗?
答案 0 :(得分:4)
您可以将类模板实例化为“已缓存”或“已存储”。更正式地讲,类模板每个翻译单元有a single point of instantiation。
所以当您写:
struct Bar;
static_assert( !has_size<Bar>::value, "Bar has a size"); // #1
struct Bar {};
static_assert( !has_size<Bar>::value, "Bar has a size"); // #2
has_size<Bar>
在#1
处实例化。那是它的 only 实例化点。在#2
,我们不会“重做”该计算-因此 still 为假。如果我们从其他翻译部门再次进行此操作(以给出不同答案的方式),则会形成错误的格式(无需诊断),但是在这种情况下-这是一个格式正确的程序。
注释掉#1
时,has_size<Bar>
的实例化点变为#2
。在程序的那个点,Bar
已完成,因此has_size<Bar>
现在为true_type
...,因此触发了静态断言。
答案 1 :(得分:3)
c ++中的每个完整类型T
都具有sizeof(T)>0
或更简单地说sizeof(T)
是有效的表达式,has_size
使用它来检测某种类型是否是完整的,或者不是并通过SFINAE完成。
第一个static_assert
struct Bar;
static_assert( !has_size<Bar>::value, "Bar has a size"); // (1)
导致has_size<Bar>
的实例化在实例化Bar
时尚未完成,这导致sizeof(T) > 0
的第二个专业化中的测试has_size
失败,这种失败的方法是使用满足has_size : std::false_type
的主模板has_size<Bar>::value == false
的定义。
第二个static_assert
struct Bar {};
static_assert( !has_size<Bar>::value, "Bar has a size"); // (2)
已评估,再次请求专业化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
完整性变化的特征,您可以选择:
template <class T>
constexpr auto has_size(int) -> decltype((void)sizeof(T), bool{})
{ return true; }
template <class T>
constexpr auto has_size(...) -> bool
{ return false; }
struct Bar;
static_assert( !has_size<Bar>(0), "Bar has a size");
struct Bar {}; // define bar
static_assert( !has_size<Bar>(0), "Bar has a size"); // fail