Я думаю о концепциях как о некоем мета-интерфейсе. Они классифицируют типы по своим способностям. Следующая версия C ++ содержит нативные концепции. Я не понимал этого, пока не наткнулся на концепции C ++ 1x и то, как они позволяют объединять разные, но не связанные между собой типы. Представьте, что у вас есть Range
интерфейс. Вы можете смоделировать это двумя способами. Одним из них является отношение подтипа :
class Range {
virtual Iterator * begin() = 0;
virtual Iterator * end() = 0;
virtual size_t size() = 0;
};
Конечно, каждый класс, производный от которого реализует интерфейс Range и может использоваться с вашими функциями. Но теперь вы видите, что это ограничено. Как насчет массива? Это тоже диапазон!
T t[N];
begin() => t
end() => t + size()
size() => N
К сожалению, вы не можете получить массив из этого класса Range, реализующего этот интерфейс. Вам нужен дополнительный метод ( перегрузка ). А как насчет сторонних контейнеров? Пользователь вашей библиотеки может захотеть использовать свои контейнеры вместе с вашими функциями. Но он не может изменить определение своих контейнеров. Здесь в игру вступают понятия:
auto concept Range<typename T> {
typename iterator;
iterator T::begin();
iterator T::end();
size_t T::size();
}
Теперь вы говорите что-то о поддерживаемых операциях некоторого типа, которые могут быть выполнены, если T
имеет соответствующие функции-члены. В вашей библиотеке вы написали бы функцию generic. Это позволяет вам принимать любой тип , если поддерживает необходимые операции:
template<Range R>
void assign(R const& r) {
... iterate from r.begin() to r.end().
}
Это отличный вид заменяемости . Любой тип будет соответствовать требованиям, соответствующим этой концепции, а не только тем типам, которые активно реализуют некоторый интерфейс. Следующий стандарт C ++ идет дальше: он определяет концепцию Container
, которая будет соответствовать простым массивам (чем-то, что называется концептуальная карта , которая определяет, как какой-то тип соответствует некоторому концепту) и другие, существующие стандартные контейнеры.
Причина, по которой я поднял этот вопрос, заключается в том, что у меня есть шаблонный контейнер, в котором сами контейнеры имеют иерархические отношения. Я хотел бы написать алгоритмы, которые используют эти контейнеры, не заботясь о том, что это за конкретный контейнер. Кроме того, некоторым алгоритмам было бы полезно знать, что тип шаблона удовлетворяет определенным понятиям (например, Comparable).
Вы можете сделать оба с шаблонами. Вы можете сохранять свои иерархические отношения для совместного использования кода, а затем писать алгоритмы в общем виде. Например, сообщить, что ваш контейнер сопоставим. Это как стандартные категории итератора с произвольным доступом / пересылкой / выводом / вводом:
// tag types for the comparator cagetory
struct not_comparable { };
struct basic_comparable : not_comparable { };
template<typename T>
class MyVector : public BasicContainer<T> {
typedef basic_comparable comparator_kind;
};
/* Container concept */
T::comparator_kind: comparator category
На самом деле это достаточно простой способ сделать это. Теперь вы можете вызвать функцию, и она будет перенаправлена на правильную реализацию.
template<typename Container>
void takesAdvantage(Container const& c) {
takesAdvantageOfCompare(c, typename Container::comparator_kind());
}
// implementation for basic_comparable containers
template<typename Container>
void takesAdvantage(Container const& c, basic_comparable) {
...
}
// implementation for not_comparable containers
template<typename Container>
void takesAdvantage(Container const& c, not_comparable) {
...
}
На самом деле для реализации этого могут использоваться разные методы. Другой способ - использовать boost::enable_if
для включения или отключения разных реализаций каждый раз.