Каждый завершенный тип T
в c ++ имеет 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
fail, этот сбой прибегает к использованию определения первичного шаблона 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
является действительным и верным, чтоich выберите специализацию 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