Страницы

Поиск по вопросам

среда, 17 октября 2018 г.

Шаблоны в C++ Ограничение типа

Есть шаблон, например : template
Подскажите, как мне ограничить тип, чтобы typename было только либо int, либо double


Ответ

Есть четыре вида шаблонов - шаблоны функций, классов, переменных и псевдонимы типов (using), для разных видов шаблонов ограничения можно задавать по разному.
Само условие проверки лучше поместить в отдельный trait, чтобы не повторять это условие в каждом шаблоне на который надо наложить ограничения:
template constexpr bool is_int_or_double_v = std::is_same::value || std::is_same::value;
static_assert
Если цель - это выдать ошибку компиляции, то внутри тела шаблона функции и класса можно использовать static_assert Для шаблонов переменных надо использовать вспомогательную функцию, в которой будет static_assert, для псевдонимов (alias) можно использовать вспомогательный класс.
template struct X { static_assert(is_int_or_double_v, ""); };
template void f() { static_assert(is_int_or_double_v, ""); }
Однако в шаблонах класса static_assert срабатывает только при создании объектов, и он не помешает написать X* x;
std::enable_if_t в параметре шаблона
Для генерации ошибки компиляции можно добавить шаблонный параметр со значением по умолчанию, но это меняет арность шаблона.
template>> struct X {};
template>> void f() {}
template>> using id = T;
template>> constexpr T v = 0;
Такую проверку можно обойти, явно указав этот параметр шаблона: X
Прочие способы использования std::enable_if_t
std::enable_if_t можно использовать везде где ожидается тип - в базовых классах, возвращаемых значениях функций, параметрах по умолчанию функций, в псевдонимах:
struct empty {}; template struct X : std::enable_if_t, empty> {};
template using id = std::enable_if_t, T>;
template std::enable_if_t, void> f() {}
template void f(std::enable_if_t>* = 0) {} // между * и = нужен пробел
template constexpr T v = (std::enable_if_t>(), 0); // оператор "запятая"
SFINAE
Функции могут быть перегружены, по этому для функций ошибка подстановки шаблона приводит только к тому что этот шаблон убирается из списка перегрузки (SFINAE). Это можно использовать совместно с удалением функций:
template>, class=void> void f() = delete; // ^ отрицание ^ третий параметр
template>> void f() {}
Для типов отличных от int и double будет использована первая перегрузка, которая удалена. Для int и double будет использована вторая. Дополнительный (третий) параметр шаблона нужен чтобы шаблоны как-то отличались - в данном случае у них разная арность.
Этот код можно записать более просто:
template>> void f() = delete; // ^ отрицание
template void f() {}
Тогда при использовании int и double первая (удаленная) перегрузка будет прятаться, а при использовании других типов - она будет видна, и будет возникать ошибка "неоднозначный вызов перегруженной функции", потому что будут видны обе перегрузки.
Разумеется вторая перегрузка не обязательно должна быть удаленной. Можно использовать механизм SFINAE для того чтобы иметь одну перегрузку для int и double, а другую перегрузку - для остальных типов. Как писалось ранее, перегрузки должны чем-то отличаться, по этому у них должны быть разные сигнатуры, или разное количество или виды параметров шаблона:
template>> void f() { /* int или double */ }
template>, class=void> void f() { /* другие типы */ } // или template>, int=0> void f() { /* другие типы */ } // или template>> void f(int=0) { /* другие типы */ }
В будущем (С++17 или позже) можно будет использовать концепции, которые заменят большинство использований enable_if, однако это будет нескоро (скорей всего вместе с Ranges TS).

Комментариев нет:

Отправить комментарий