Понимание повторной оценки типов / значений шаблонов - PullRequest
0 голосов
/ 08 декабря 2018

У меня есть следующий код, и я не понимаю, почему последний !has_size<Bar>::value оценивается как true, только если я не закомментирую точно такой же static_assert( !has_size<Bar>::value, ...) до определения 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");

Я хочу принять некоторые шаблонные решения позже, основываясь на значении has_size<Bar>.Поведение одинаково для msvc, gcc и clang.Я пытаюсь выяснить, является ли это намеренным и хорошо задокументированным поведением, или я брожу по земле UB или какой-то другой беде, полагаясь на это поведение.Мысли?

Ответы [ 2 ]

0 голосов
/ 09 декабря 2018

Вы можете думать об экземплярах шаблонов классов как о «кэшированных» или «запоминаемых».Более формально, шаблоны классов имеют одну точку создания на единицу перевода.

Поэтому, когда вы пишете:

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 мы не «повторяем» это вычисление, поэтому все еще false.Если мы сделаем это снова из другого блока перевода, таким образом, чтобы дать другой ответ, это было бы неправильно (не требуется диагностика), но в этой ситуации - это правильно сформированная программа.

Когда вы комментируете #1, теперь точка создания has_size<Bar> становится #2.И в этой точке в программе, Bar завершено, поэтому has_size<Bar> теперь равно true_type ... так что срабатывает статическое утверждение.

0 голосов
/ 09 декабря 2018

Каждый завершенный тип 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
...