Страницы

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

воскресенье, 8 декабря 2019 г.

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

#cpp


Есть шаблон, например : template 

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


Ответы

Ответ 1



Есть четыре вида шаблонов - шаблоны функций, классов, переменных и псевдонимы типов (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).

Ответ 2



Как вариант можно сделать проверку через static_assert : template class Class { static_assert(std::is_same::value, "template instantiation of Class are not int"); }; варианты проверок типа так же могут быть is_integral, is_base_of

Ответ 3



Если разрешенных типов совсем немного, можно обойтись специализациями. // Шаблонная функция template void f(T); template <> void f(int) {} template <> void f(double) {} // Шаблонный класс template class C; template <> class C {}; template <> class C {}; Для функций, однако, (как заметил @Abyx) для достижения нужного эффекта можно обойтись перегрузками, т.е. убрать строки template <>: template void f(T); void f(int) {} void f(double) {} В таком случае, вызов с любым другим типом, кроме int или double приведет для функций к ошибке линковщика. Т.к. общий шаблон функции не имеет реализации. А для классов - к ошибке компиляции. При этом, стоит заметить, что специализации и перегруженные функции всё же могут существовать и параллельно: #include template void f(T); template <> void f(int) { std::cout << "spec\n"; } void f(int) { std::cout << "fun\n"; } int main() { f(42); // spec f(42); // fun } Для шаблонного класса (помимо варианта @Yuriy Orlov со static_assert) можно использовать специализацию в связке с std::enable_if: #include template class X; template class X::value || std::is_same::value >::type> { }; int main() { X xd; // ok X xi; // ok X xl; // error } Хотя вариант со static_assert выглядит, имхо, элегантнее.

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

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