首页 文章

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

提问于
浏览
4

我有以下代码,我不明白为什么最后 !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 回答

  • 3

    您可以将类模板实例化视为"cached"或"memoized."更正式地说,类模板每个翻译单元具有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 实例化 . 这是它唯一的实例化点 . 在 #2 ,我们没有"redo"那个计算 - 所以它仍然是假的 . 如果我们从一个不同的翻译单元再次这样做,以一种不同的答案,那将是不正确的(不需要诊断),但在这种情况下 - 这是一个结构良好的程序 .

    当您注释掉 #1 时,现在 has_size<Bar> 的实例化点变为 #2 . 在程序中的那一点上, Bar 已经完成,所以 has_size<Bar> 现在是 true_type ......所以静态断言会触发 .

  • 4

    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 是不完整的,这导致 has_size 的第二个特化中的测试 sizeof(T) > 0 失败,这个故障依赖于使用满足 has_size<Bar>::value == false 的主模板 has_size : std::false_type 的定义 .

    当第二个 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
    

相关问题