Примечание: в конечном итоге это выглядит скорее как напыщенная речь, а не как правильный ответ ... Я все же испытывал некоторые трудности с чтением предыдущих ответов, поэтому, пожалуйста, извините;)
Сначалаисторически черты класса делаются с использованием шаблонных структур, поскольку они предшествуют 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_*
намного проще!
Лично я считаю, что это неудачно.Но я, вероятно, гораздо больше стремлюсь пересмотреть существующий код, который написал, чем средняя корпорация.