Написание и проверка ваших собственных концепций на С ++

Я пишу библиотеку 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++ с поддержкой реальных концепций, вам определенно следует использовать ее вместо этого: среди других приятных функций вы сможете получать понятные сообщения об ошибках, если сделаете что-то не так, что не обязательно что-то вы выйдете из методов, упомянутых выше. В заключение, если вы действительно решили сделать что-то подобное, крайне важно провести тщательное тестирование: действительно легко что-то ошибиться и действительно сложно понять, как это исправить, когда ваш класс уже используется в другом месте вашего кода.

Другие вопросы по тегам