Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding repeated evaluation of template types/values

I have the following code, and I do not understand why the last !has_size<Bar>::value evaluates to true only if I don't comment out the exact same static_assert( !has_size<Bar>::value, ...) prior to defining Bar

#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");

I want to make some templating decisions later based on the value of has_size<Bar>. The behavior is the same across msvc, gcc, and clang. I'm trying to figure out if this is intended and well-documented behavior, or if I'm wandering into UB land or some other calamity by relying on this behavior. Thoughts?

like image 608
Tom Avatar asked Mar 04 '23 22:03

Tom


1 Answers

You can think of class template instantiations as being "cached" or "memoized." More formally, class templates have a single point of instantiation per translation unit.

So when you write:

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> is instantiated at #1. That is its only point of instantiation. At #2, we don't "redo" that calculation - so it is still false. If we did this again from a different translation unit, in a way that would give a different answer, that would be ill-formed (no diagnostic required), but in this situation - this is a well-formed program.

When you comment out #1, now the point of instantiation of has_size<Bar> becomes #2. And at that point in the program, Bar is complete, so has_size<Bar> is now true_type... so the static assertion triggers.

like image 145
Barry Avatar answered Mar 16 '23 00:03

Barry