Почему type_traits реализован с помощью специализированных шаблонных структур вместо constexpr? - PullRequest
8 голосов
/ 17 января 2012

Есть ли причина, по которой стандарт определяет их как шаблон struct s вместо простого логического constexpr?

В дополнительном вопросе, на который, вероятно, будет дан хороший ответ на главный вопрос, как можно сделать enable_if материал с неструктурными версиями?

Ответы [ 5 ]

18 голосов
/ 17 января 2012

Одной из причин является то, что функции constexpr не могут предоставить вложенный элемент type, что полезно в некоторых ситуациях метапрограммирования.

Чтобы было ясно, я говорю не только о черты преобразования (например, make_unsigned), которые производят типы и, очевидно, не могут быть выполнены constexpr функциями. Все признаки типа предоставляют такой вложенный type член, даже признаки унарного типа и признаки двоичного типа .Например, is_void<int>::type - это false_type.

Конечно, это можно обойти с помощью std::integral_constant<bool, the_constexpr_function_version_of_some_trait<T>()>, но это будет не так практично.

В любом случае, если выдействительно хочу подобный функции синтаксис, это уже возможно.Вы можете просто использовать конструктор признаков и воспользоваться неявным преобразованием constexpr в integral_constant:

static_assert(std::is_void<void>(), "void is void; who would have thunk?");

Для признаков преобразования вы можете использовать псевдоним шаблоначтобы получить что-то близкое к этому синтаксису:

template <bool Condition, typename T = void>
using enable_if = typename std::enable_if<Condition, T>::type;
// usage:
// template <typename T> enable_if<is_void<T>(), int> f();
//
// make_unsigned<T> x;
4 голосов
/ 17 января 2012

Примечание: в конечном итоге это выглядит скорее как напыщенная речь, а не как правильный ответ ... Я все же испытывал некоторые трудности с чтением предыдущих ответов, поэтому, пожалуйста, извините;)

Сначалаисторически черты класса делаются с использованием шаблонных структур, поскольку они предшествуют constexpr и decltype.Без этих двух было немного больше работы с функциями, хотя различные реализации библиотеки is_base_of имели для внутреннего использования функций, чтобы получить право наследования.

Что преимущества использования функций?

  • наследование просто работает.
  • синтаксис может быть более естественным (typename ::type выглядит глупо TM )
  • большое количество признаков теперь устарели

На самом деле, наследование, вероятно, является основным пунктом против черт класса.Как чертовски неприятно, что вам нужно специализировать все ваши производные классы, например momma .Очень надоедливый.С помощью функций вы просто наследуете черты и можете специализироваться , если хотите .

Каковы недостатки ?

  • упаковка!Черта структуры может включать в себя несколько типов / констант одновременно.

Конечно, можно утверждать, что это действительно раздражает: специализируя iterator_traits, вы просто так часто безвозмездно наследуете от std::iterator_traits просто , чтобы получить значение по умолчанию.Различные функции обеспечивают это просто естественным образом.

Может ли это работать?

Ну, одним словом, где все будет основано на constexpr, кроме enable_if (но потом, это не черта характера), вы бы хотели:

template <typename T>
typename enable_if<std::is_integral(T()) and
                   std::is_signed(T())>::type

Примечание: я не использовал std::declval здесь, потому что это требует неоцененного контекста (то есть, sizeof или decltype в основном).Таким образом, одно дополнительное требование (не сразу видно) заключается в том, что T является конструируемым по умолчанию.

Если вы действительно хотите, есть хак:

#define VALUE_OF(Type_) decltype(std::declval<T>())

template <typename T>
typename enable_if<std::is_integral(VALUE_OF(T)) and
                   std::is_signed(VALUE_OF(T))>::type

А что, если янужен тип, а не константа?

decltype(common_type(std::declval<T>(), std::declval<U>()))

Я тоже не вижу проблемы (и да, здесь я использую declval).Но ... передача типов не имеет ничего общего с constexpr;Функции constexpr полезны, когда они возвращают значения , которые вас интересуют. Конечно, можно использовать функции, которые возвращают сложные типы, но они не являются constexpr, и вы не используете значениетип.

А что, если мне нужно связать трейсы и типы?

По иронии судьбы, это где функции светят:)

// class version
template <typename Container>
struct iterator { typedef typename Container::iterator type; };

template <typename Container>
struct iterator<Container const> {
  typedef typename Container::const_iterator type;
};

template <typename Container>
struct pointer_type {
  typedef typename iterator<Container>::type::pointer_type type;
};


template <typename Container>
typename pointer_type<Container>::type front(Container& c);

// Here, have a cookie and a glass of milk for reading so far, good boy!
// Don't worry, the worse is behind you.


// function version
template <typename Container>
auto front(Container& c) -> decltype(*begin(c));

Какие!Жулик!Черты не определены!

Хм ... на самом деле, в этом суть.С decltype большое количество признаков только что стало избыточным .

DRY !

Наследование просто работает!

Возьмите базовую иерархию классов:

struct Base {};
struct Derived: Base {};
struct Rederived: Derived {};

И определите признак:

// class version
template <typename T>
struct some_trait: std::false_type {};

template <>
struct some_trait<Base>: std::true_type {};

template <>
struct some_trait<Derived>: some_trait<Base> {}; // to inherit behavior

template <>
struct some_trait<Rederived>: some_trait<Derived> {};

Примечание: предполагается, что признакдля Derived не указывается напрямую true или false, а вместо этого принимается поведение от его предка.Таким образом, если предок меняет позицию, вся иерархия следует автоматически.В большинстве случаев, поскольку базовая функциональность предоставляется предком, имеет смысл следовать его чертам.Тем более для типовых признаков.

// function version
constexpr bool some_trait(...) { return false; }

constexpr bool some_trait(Base const&) { return true; }

Примечание : использование многоточия является преднамеренным, это универсальная перегрузка.Функция шаблона будет лучше соответствовать другим перегрузкам (преобразование не требуется), тогда как многоточие всегда является наихудшим соответствием, гарантирующим, что оно подберет только те, для которых никакая другая перегрузка не подходит.

Полагаю, это не нужночтобы уточнить, насколько более кратким является последний подход?Вы не только избавляетесь от беспорядка template <>, но и получаете наследство бесплатно.

Можно ли реализовать enable_if так?

Не знаюдумаю, к сожалению, но, как я уже сказал: это не черта.И версия std прекрасно работает с constexpr, потому что она использует аргумент bool, а не тип:)

Итак Почему ?

Ну, единственная техническая причина заключается в том, что большая часть кода уже опирается на ряд признаков, которые исторически предоставлялись кактипы (std::numeric_limit), так что согласованность будет диктовать это.

Более того, это делает миграцию из boost::is_* намного проще!

Лично я считаю, что это неудачно.Но я, вероятно, гораздо больше стремлюсь пересмотреть существующий код, который написал, чем средняя корпорация.

3 голосов
/ 17 января 2012

Одной из причин является то, что предложение type_traits старше предложения constexpr.

Еще одно - вы можете добавлять специализации для своих типов, если это необходимо.

2 голосов
/ 17 января 2012

Я бы сказал, что основная причина в том, что type_traits уже был частью tr1 и, следовательно, в основном гарантированно попадал в стандарт в более или менее такой же форме, поэтому он предшествует constexpr.Другие возможные причины:

  • Наличие признаков в качестве типов позволяет перегружать функции для типа признака
  • Многие признаки (например, remove_pointer) определяют type вместоvalue, поэтому они должны быть выражены таким образом.Наличие различных интерфейсов для признаков, определяющих значения, и признаков, определяющих типы, кажется несущественным
  • templated structs может быть частично специализированным, в то время как функции не могут, так что это может упростить реализацию некоторых признаков

По второму вопросу: поскольку enable_if определяет type (или нет, если он передан false), вложенный typedef внутри struct - это действительно путь к

2 голосов
/ 17 января 2012

Возможно, потому что в boost уже была версия type_traits, которая была реализована с помощью шаблонов.

И все мы знаем, сколько людей в комитете по стандартизации копируют boost.

...