了解模板类型/值的重复评估

时间:2018-12-08 19:33:26

标签: c++ c++11 templates static-assert

我有以下代码,并且我不理解为什么只有在定义!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国或其他灾难。有想法吗?

2 个答案:

答案 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