Страницы

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

вторник, 28 января 2020 г.

Ограничения на параметр шаблона

#cpp #шаблоны_с++ #constraints


Есть шаблонный класс массива

template class Array;


в котором реализована функция сортировки, которая требует наличия у параметра шаблона
функции CompareTo(_Ty& other);
Так вот, как указать в определении шаблона что у параметра _Ty обязательно будет
функция ComareTo(_Ty& other).
Что то подобное реализовано в C#, но не знаю как и возможно ли такое проделать на плюсах?
    


Ответы

Ответ 1



Вот мой вариант: #include template class has_CompareTo_self { typedef char one[1]; typedef char two[2]; template struct type_check; template static one& test( type_check* ); template static two& test(...); public: enum { value = sizeof(test(0)) == sizeof(one) }; }; template class Array { static_assert(has_CompareTo_self<_Ty>::value, "no appropriate CompareTo found"); }; Преимущество этого варианта в том, что он проверяет сигнатуры. То есть, принимает такой класс: class Good { public: int CompareTo(Good& other); }; но не такой: class Bad { public: long CompareTo(Bad& other); }; Проверка: http://ideone.com/drYcz4 Давайте попробую объяснить, как эта шаблонная магия работает, чтобы мой вклад был больше, чем простая адаптация этого ответа. Главной идеей всех таких штук является принцип substitution failure is not an error (SFINAE): если при разворачивании шаблона получается несколько перегрузок функции, но одна из них не компилируется, то это не ошибка, а просто молча игнорируется. Сначала простое: one и two — две структуры данных гарантированно разного размера. Это нам понадобится в дальнейшем. Больше от этих структур ничего не требуется. Теперь, центральный компонент кода — объявление template static one& test( type_check* ); Оно скомпилируется лишь если type_check имеет смысл. Для этого у класса C как минимум должен быть метод CompareTo. Затем, у нас есть хитрый шаблон type_check, принимающий аргументами тип F, и константное выражение типа F! То есть для того, чтобы упоминание этого шаблона скомпилировалось, нужно, чтобы выражение, переданное ему вторым аргументом, имело такой тип, как в первом аргументе. В нашем случае это накладывает требование: &C::CompareTo должна иметь тип int (C::*)(C&) (то есть, функция-член, принимающая аргумент типа C& и возвращающая int). Итак, это самое центральное объявление скомпилируется тогда и только тогда, когда у класса C есть функция-член int C::CompareTo(C&) — то, что нам и надо! Как это работает дальше? У нас есть ещё одна перегрузка: template static two& test(...); которая компилируется всегда. Посмотрим на строку enum { value = sizeof(test(0)) == sizeof(one) }; Она объявляет внутренний enum с одним значением value. Правую часть (то есть, значение самого value) компилятор может вычислить, т. к. это константное выражение времени компиляции! Каков тип test(0)? Если центральная строка скомпилировалась, то 0 подходит в качестве указателя на структуру type_check<...>, и тип возвращаемого значения — one. Если нет, то работает вторая перегрузка, и тип возвращаемого значения — two. Различить эти два случая помогает сравнение размеров: sizeof(one) не равен sizeof(two). Таким образом, в зависимости от компилируемости центральной строки значение value — true или false. Вся эта механика спрятана внутри шаблона has_CompareTo_self, чтобы не засорять нормальным людям область видимости. Дальше всё просто: мы применяем static_assert в теле нашего шаблона Array. Конец! Да, я тоже считаю, что это неподдерживаемый код, и что метапрограммирование должно иметь менее вырвиглазный синтаксис.

Ответ 2



В C++11 можно сделать static_assert с std::is_base_of. Придется сделать базовый класс с методом ComareTo(_Ty& other); и проверять, является ли этот класс базовым классом для текущей спецификации шаблона.

Ответ 3



В определении шаблона - пока никак. В настоящий момент такой функциональности просто не существует. Она прорабатывается на уровне комитета, но не вошла в стандарт, пока. Существуют методы, с помощью которых можно это сделать. Но они далеки от идеала и используют довольно зубодробительный код. Но это не самое страшное, самое страшное, что они не являются общими и для каждой функции нужно писать свой код. Можете посмотреть на английском SO пример ответа с таким кодом.

Ответ 4



Установить ограничение на параметр шаблона просто так (из вредности) нельзя. Но зато можно проверить наличие у параметра шаблона определённой функции и в случае отсутствия таковой переключиться на отдельную специализацию шаблона. #include using namespace std; template class ensure_CompareTo { // используем SFINAE, чтобы определить наличие функции // когда функция есть, выбирается первый шаблон, когда нет - второй template static T test(char (*)[sizeof(&U::CompareTo)]); template static void test(...); public: // этот алиас зависит от того, какой шаблон был выбран с помощью SFINAE // он равен либо исходному типу, либо void using type = decltype(test(0)); }; // базовый шаблон массива // второй параметр выводится автоматически из первого // он либо совпадает с ним, когда требуемая функция есть, либо равен void template ::type> class Array { public: static const char* desc() { return "отсортированный массив"; } }; // специализация шаблона массива для случая, когда второй параметр, выводимый // автоматически из первого, оказался равным void, т.е. когда требуемой // функции в параметре шаблона не оказалось template class Array { public: static const char* desc() { return "просто массив"; } }; class A { public: int CompareTo(A& other); }; class B { }; int main() { cout << Array::desc() << endl; cout << Array::desc() << endl; return 0; }

Ответ 5



Не уверен, что это возможно. Но почему обязательно привязывать интерфейс сравнения к шаблону класса? Почему бы не сделать это как в Java или C#? Там есть параметризованный интерфейс Comparable (IComparable для C#), который реализуют классы, которые должны иметь возможность сравнения с экземплярами других классов. Таким образом вы получите даже большую гибкость, обеспечив возможность сравнения одновременно с несколькими типами данных: template class Comparable { public: virtual bool CompareTo(T* other) = 0; }; template class Array : Comparable >, Comparable { public: Array() {} bool CompareTo(Array* other) { return false; } bool CompareTo(int* other) { return false; } }; int main() { Array a; Array b; int c = 123; a.CompareTo(&b); a.CompareTo(&c); return 0; }

Ответ 6



Если мы хотим просто проверить наличие члена - достаточно его вызвать. Мы не хотим выполнять этот код, соответственно мы будем вызывать его в контексте который не приводит к выполнению кода, а именно sizeof, decltype или noexcept. Код, который будет выполняться, во всех случаях выглядит одинаково - создание объекта с помощью std::declval() и вызов метода: std::declval().CompareTo(std::declval()) Если надо проверить несколько свойств, можно использовать оператор "запятая": std::declval().f(), std::declval().g() При использовании decltype мы получаем тип, и его можно использовать как дополнительный параметр шаблона: template().CompareTo(std::declval()))> struct Array {}; Этому decltype можно сделать псевдоним (alias) template using has_CompareTo = decltype(std::declval().CompareTo(std::declval())); template> struct Array {}; Для sizeof и noexcept мы тоже можем использовать параметр шаблона: template().CompareTo(std::declval()), 0)> struct Array {}; , 0 на конце выражения нужно на случай если результат CompareTo имеет тип void. Также мы можем написать это как член класса: template struct Array { enum { has_CompareTo = noexcept(std::declval().CompareTo(std::declval()), 0) }; // или using has_CompareTo = decltype(std::declval().CompareTo(std::declval())); }; Вариант с параметром шаблона интересен тем что он включает SFINAE, но в данном случае оно не нужно.

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

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