Почему специализация type_trait может привести к неопределенному поведению?

Обсуждение

Согласно стандартному §20.10.2/1 Header <type_traits> synopsis [meta.type.synop]:

1 Поведение программы, которая добавляет специализации для любого из шаблонов классов, определенных в этом подпункте, не определено, если не указано иное.

Этот конкретный пункт противоречит общему представлению о том, что STL должен быть расширяемым, и не позволяет нам расширять признаки типа, как в примере ниже:

namespace std {
template< class T >
struct is_floating_point<std::complex<T>> : std::integral_constant
         <
         bool,
         std::is_same<float, typename std::remove_cv<T>::type>::value  ||
         std::is_same<double, typename std::remove_cv<T>::type>::value ||
         std::is_same<long double, typename std::remove_cv<T>::type>::value
         > {};
}

РЕАЛЬНАЯ ДЕМО

где std::is_floating_point расширяется для обработки числа complex с базовым типом с плавающей запятой.

Вопросы

  1. По каким причинам комитет по стандартизации решил, что признаки типа не должны быть специализированными.
  2. Планируется ли снятие этого ограничения в будущем?

person 101010    schedule 17.08.2014    source источник
comment
@billz как насчет этого? В программе размещены экспонаты УБ.   -  person 101010    schedule 17.08.2014
comment
Почему не struct is_floating_point<std::complex<T>> : public std::is_floating::point<T> {};?   -  person Manu343726    schedule 17.08.2014
comment
@ Manu343726 Manu343726 yes сортирует и лучше, но также не имеет значения, поскольку ваша версия также демонстрирует поведение undefined.   -  person 101010    schedule 17.08.2014
comment
Даже если бы это было разрешено, специализация std::is_floating_point и заставление всей единицы перевода считать std::complex типом с плавающей запятой только для того, чтобы вы могли проверить, является ли std::complex или с плавающей запятой в одной из ваших функций, это все равно, что взорвать свой дом, чтобы получить что-то свежее. воздух внутри. Классы признаков типа запрашивают фундаментальные свойства типов и являются основными строительными блоками для шаблонных метапрограмм. Нет смысла позволять вам добавлять для них специализации.   -  person T.C.    schedule 17.08.2014


Ответы (2)


Для категорий основных типов, одной из которых является is_floating_point, существует инвариант дизайна:

Для любого заданного типа T ровно одна из основных категорий типов имеет член-значение, значение которого равно true.

Ссылка: (20.10.4.1 Основные категории типов [meta.unary.cat])

Программисты могут полагаться на этот инвариант в универсальном коде при проверке неизвестного универсального типа T: т.е. если is_class<T>::value равно true, то is_floating_point<T>::value проверять не нужно. Мы гарантируем, что последний false.

Вот диаграмма, представляющая первичные и составные признаки типа (листья в верхней части этой диаграммы — это первичные категории).

http://howardhinnant.github.io/TypeHiearchy.pdf

Если бы было разрешено иметь (например) std::complex<double> ответ, верный как для is_class, так и для is_floating_point, этот полезный инвариант был бы нарушен. Программисты больше не смогут полагаться на тот факт, что если is_floating_point<T>::value == true, то T должно быть одним из float, double или long double.

Теперь есть некоторые трейты, где стандарт «говорит иначе», и разрешена специализация на пользовательских типах. common_type<T, U> такая черта.

Для первичных и составных черт не планируется ослаблять ограничение на специализацию этих черт. Это поставит под угрозу способность этих трейтов точно и однозначно классифицировать каждый отдельный тип, который может быть сгенерирован в C++.

person Howard Hinnant    schedule 17.08.2014
comment
Существует также своего рода «инвариант» в том, что черты категории первичного типа также отражают истину. То есть такая категория, как «тип с плавающей запятой», конкретно описана в Стандарте, и они практически не расширяемы и не настраиваются пользователем. Таким образом, специализация, определяемая пользователем, будет либо повторять очевидное, либо лгать. - person Luc Danton; 17.08.2014
comment
@Deduplicator: Хорошо, я удалил вложение !. - person Howard Hinnant; 17.08.2014
comment
Как насчет исправления ошибок в стандарте? Например, сделать std::complex trivially_default_constructible (как мы знаем, должно быть) godbolt.org/z/lQlMTy . - person alfC; 01.04.2020

Добавление к ответу Говарда (с примером).

Если бы пользователям было позволено специализировать черты типа, они могли бы лгать (намеренно или по ошибке), и Стандартная библиотека больше не могла бы гарантировать правильность своего поведения.

Например, когда копируется объект типа std::vector<T>, оптимизация, которую делают популярные реализации, заключается в вызове std::memcpy для копирования всех элементов, при условии, что T тривиально копируется. Они могут использовать std::is_trivially_copy_constructible<T>, чтобы определить, безопасна ли оптимизация или нет. Если нет, то реализация возвращается к безопасному, но более медленному методу, который перебирает элементы и вызывает конструктор копирования T.

Теперь, если кто-то специализируется на std::is_trivially_copy_constructible для T = std::shared_ptr<my_type> следующим образом:

namespace std {
    template <>
    class is_trivially_copy_constructible<std::shared_ptr<my_type>> : std::true_type {
    };
}

Тогда копирование std::vector<std::shared_ptr<my_type>> было бы катастрофой.

В этом виновата не реализация стандартной библиотеки, а автор специализации. В какой-то степени это то, о чем говорит цитата, предоставленная ОП: «Это твоя вина, а не моя».

person Cassio Neri    schedule 17.08.2014
comment
Пример очень поучительный, хорошо все проясняет. - person 101010; 17.08.2014
comment
Я не думаю, что этот пример правильно иллюстрирует проблему. Этот пример создает неработающую программу не потому, что мы специализируемся на типе, который мы не должны специализировать, а потому, что мы предоставляем ложную информацию о типе (общий указатель). Поведение программы было бы столь же неопределенным, если бы мы наследовались от false_type, в то время как практически она работала бы нормально, потому что специализация давала бы правильную информацию. В качестве примечания, copy ctor также должен быть правильно реализован, чтобы библиотека гарантировала правильное векторное копирование, но его реализация не является незаконной. - person S. Kaczor; 17.03.2021