Наследование от стандартных свойств типа библиотеки

При написании пользовательских типов типов я часто выводил их из стандартных типов типов библиотеки следующим образом:

template<typename T>
struct some_type_trait:
    std::is_arithmetic<T>
{};

Однако иногда я задаюсь вопросом, является ли наследование типа type типа стандартного типа библиотеки более чистым:

template<typename T>
struct some_type_trait:
    std::is_arithmetic<T>::type
{};

Общая идея заключается в том, что в конечном итоге имеет значение только наследование от std::bool_constant, но факт, что мы наследуем от std::is_arithmetic в первом примере, а не непосредственно от std::bool_constant (как во втором случае) можно наблюдать с помощью полиморфизма или utilies типа std::is_base_of.

Суть в том, что наследование непосредственно из bool_constant через type тип члена становится более чистым, потому что это именно то, что мы хотим. Однако наследование от std::is_arithmetic немного короче и обеспечивает по сути одно и то же поведение. Итак... есть ли какое-то тонкое преимущество, которое я мог бы потерять при выборе того или другого (правильность, время компиляции...)? Существуют ли тонкие сценарии, где наследование от std::is_arithmetic может изменить поведение приложения, по сравнению с наследованием непосредственно из базового bool_constant?

Ответы

Ответ 1

Вторая имеет второстепенную ловушку утечки деталей реализации; кто-то может проверить через перегрузку функции, если вы наследуете от is_arithmetic<int> или что-то еще. Это может "работать" и приводить к ложным срабатываниям.

Это очень незначительная проблема.

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

Ни один из ваших проектов не складывается хорошо. Вместо этого:

template<class T>
struct some_type_trait:
  std::integral_constant<bool,
    std::is_arithmetic<T>{}
  >
{};

может быть расширен, так как мы можем поместить там какое-либо выражение.

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

constexpr bool all_of() { return true; }
template<class...Bools>
constexpr bool all_of(bool b0, Bools...bs) {
    return b0 && all_of(bs...);
}
template<class T, template<class...>class...Requirements>
struct Requires : std::integral_constant<bool,
  Requirements<T>{} &&...
  // in C++11/14, something like: all_of(Requirements<T>::value...)
> {};

Тогда получим:

template<class T>
using some_type_trait = Requires<T, std::is_arithmetic>;

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

template<class T>
void test( std::true_type passes_test, T t ) {
  std::cout << t+0 << "\n";
}
template<class T>
void test(T t) {
  return test(some_type_trait<T>{}, t);
}
int main() {
  test(3);
  test("hello");
}

К сожалению, у нас нет эквивалента простого привязки/частичного приложения/каррирования в метапрограммировании шаблонов. Поэтому f<.> = is_base_of<X, .> трудно выразить кратко.

Пример в реальном времени, мы получаем сообщения об ошибках: лязг:

main.cpp:23:10: note: candidate function [with T = const char *] not viable: no known conversion from 'some_type_trait<const char *>' (aka 'Requires<const char *, std::is_arithmetic>') to 'std::true_type' (aka 'integral_constant<bool, true>') for 1st argument

НКА:

main.cpp:28:18: note:   cannot convert 'Requires<const char*, std::is_arithmetic>()' (type 'Requires<const char*, std::is_arithmetic>') to type 'std::true_type {aka std::integral_constant<bool, true>}'

который по крайней мере приводит вас к ошибке (что const char* не is_arithmetic).