Написание и проверка ваших собственных концепций на С ++
Я пишу библиотеку C++ только для заголовков, которая довольно часто использует шаблоны. Теперь я хочу добавить некоторые проверки концепций для обработки ошибок времени компиляции, возникающих при использовании некорректных типов в параметрах шаблона.
Например, мне нужна концепция для объектов, похожих на указатели, которые могут указывать на один объект (например, std::shared_ptr), объект, похожий на указатель, который может указывать на массив (через оператор []), но не может использоваться с указателем арифметика (например, std::unique_ptr) и указатели, которые можно использовать с арифметикой указателей и так далее.
Поскольку концепции все еще не соответствуют стандарту и не поддерживаются компиляторами, мне нужно реализовать это самостоятельно. Я знаю о библиотеке Boost Concept, но по какой-то причине я не хочу добавлять ее в зависимости.
Итак, вопрос в том, как реализовать проверку некоторых требований к типу? Как это реализовано в Boost? Какие методы распространены в таких случаях?
1 ответ
Я сам сделал немного такого рода вещей, так как все еще использую C++11. По сути, способ сделать это - интенсивное использование SFINAE и хорошее знакомство со всеми этими вещами: http://en.cppreference.com/w/cpp/types
Возможно, наиболее важными из них для проверки концепции enable_if
: это шаблон, который предоставляет заданный тип возврата, если первый параметр шаблона true
и приводит к ошибке замены, если этот параметр false
:
//this one gets called only for pointers
template <typename T>
typename enable_if<is_pointer<T>::value, bool>::type do_stuff(T) {}
//this one gets called only for non-pointers
template <typename T>
typename enable_if<not is_pointer<T>::value, bool>::type do_stuff(T) {}
Если вам не нужна возможность перегружать подобные вещи, и вам нравятся читаемые сообщения об ошибках, вы должны использовать static_assert
вместо:
template <typename T>
class pointer_thingy {
static_assert(is_pointer<T>::value, "T must be a pointer");
//...
};
Теперь перейдем к более сложной части: определению ваших собственных концептуальных шаблонных вещей. Если возможно, лучший способ сделать это - просто написать их в терминах уже существующих стандартных по ссылке выше. Однако иногда вы хотите проверить вещи, которые там недоступны, например, наличие определенной операции. В этом случае SFINAE ваш друг:
template <typename T>
class is_equality_comparable {
template <typename U> static auto check(const U& u) -> typename std::conditional<
std::is_convertible<decltype(u == u), bool>::value,
std::true_type, std::false_type>::type;
static std::false_type check(...);
public:
static constexpr bool value = decltype(check(std::declval<T>()))::value;
};
Это проверяет, есть ли у определенного типа оператор равенства (operator==
) и если он возвращает что-то, что может быть использовано как bool
, Как это происходит, хотя и требует некоторого объяснения: главное, что делает этот класс, это определить check
метод, который никогда не вызывается и генерировать правильное значение с помощью вычислений check
тип возврата. Внизу класс делает именно это: он определяет тип возвращаемого значения check
когда вызывается с мнимым значением типа T
(генерируется с помощью declval
во избежание зависимости от конструкторов). Для того, чтобы это работало правильно, две перегрузки check
предоставляются: первый шаблонный, а второй использует ...
запись для того, чтобы принимать любые аргументы и иметь более низкий приоритет выбора, чем первая перегрузка. Первая перегрузка использует тип возвращаемого суффикса, чтобы он мог ссылаться на свой параметр (что делает код намного чище) и использует conditional
выбирать между true_type
а также false_type
в зависимости от того operator==
правильно возвращает то, что может быть использовано как bool
, Если operator==
не существует, первая перегрузка приводит к сбою замещения, а SFINAE гарантирует, что он незаметно отбрасывается из списка возможных перегрузок, а это означает, что гипотетический вызов check
возвращается ко второй перегрузке, которая просто возвращает false_type
,
Конечно, это только мой способ сделать это; это метод, который работает, но я не уверен, так ли это делает Boost или, если на то пошло, так это делает кто-то другой. Если вы можете использовать более новую версию C++ с поддержкой реальных концепций, вам определенно следует использовать ее вместо этого: среди других приятных функций вы сможете получать понятные сообщения об ошибках, если сделаете что-то не так, что не обязательно что-то вы выйдете из методов, упомянутых выше. В заключение, если вы действительно решили сделать что-то подобное, крайне важно провести тщательное тестирование: действительно легко что-то ошибиться и действительно сложно понять, как это исправить, когда ваш класс уже используется в другом месте вашего кода.