Основное отличие обобщений generics
от шаблонов templates
заключается в алгоритме проверки пригодности параметров для тех операций, которые с ними будут
затем производиться.
При объявлении нового обобщения эта проверка осуществляется один раз еще до создания каких-либо экземпляров этого обобщения. Такое становится возможно за счет использования в качестве параметров интерфейсов или их аналогов, задающих, что можно сделать с экземпляром параметра. А при передачe параметров при создании экземпляра обобщения эти параметры проверяются на предмет реализации (наследования) соответствующего интерфейса.
Пример обобщения Java (C#, Rust) -style. По объявлению параметра T extends Weighted
компилятор сразу определяет, что метода Compare
у объекта value
быть не может и выдаст ошибку.
interface Weighted
{
public int Weight(int mass);
}
class Test
{
public static <T extends Weighted>
void Drop(T value, int mass)
{
if (0 < value.Compare(mass)) // error
{
//...
}
}
}
online compiler
При объявлении нового шаблона происходит только базовая проверка синтаксиса, а пригодность параметров к тем действиям, которые с ними будут производиться, определяется только при создании экземпляра шаблона. В С++ в теле шаблона можно писать практически что угодно, а с тем, имеет ли это какой-то смысл, компилятор будет в каждом конкретном случае разбираться потом, только когда шаблон будет где-то использован.
template<typename T>
void Drop(T value, int mass)
{
if (0 < value.Compare(mass)) // ничего...
{
//...
}
}
//...
struct foo
{
int Weight(int) { return 0; }
};
int main()
{
Drop(foo{}, 4); // и только тут компилятор выдаст вереницу ошибок
}
Online compiler
В С++20 должны были добавить механизм обобщений, реализованный посредством концептов (Concepts
), что позволило бы упростить нахождение ошибок в шаблонах, сделать сами ошибки короче и понятнее, и ускорить компиляцию. Однако что-то пошло не так и вместо обобщений концепты по сути добавляют еще один слой генерации ошибок поверх шаблонов.
Пример С++:
#include <concepts>
template<typename T>
concept Weighted = requires(T a)
{
{ a.Weight(int{}) } -> std::same_as<int>;
};
template<Weighted T>
void Drop(T value, int mass)
{
if (0 < value.Compare(mass)) // по-прежнему ничего...
{
//...
}
}
//...
struct foo
{
int Weight(int) { return 0; }
};
int main()
{
Drop(foo{}, 4); // error
}
Online compiler
Причем я бы хотел упомянуть такой ньюанс, что возможности писать в концепте просто -> int
вместо ::std::same_as<int>
тоже лишили, вынуждая даже в простейших случаях прибегать в библиотечным концептам, что еще больше замедляет компиляцию...
Еще остались Associated Types
. Это понятие стоит в стороне от обобщений и шаблонов. В С++ это называется зависимыми (от параметров шаблона) именами. Суть в том, что вместо дополнительных параметров шаблона берутся какие-то типы, которые доступны непосредственно в ранее объявленных параметрах шаблона или могут быть получены с помощью сторонних трейтов, примененных к этим параметрам (например ::std::iterator_traits
).
Пример для С++. Вместо
template<typename Container, typename Value>
Value & Middle(Container & container);
можно писать
template<typename Container>
typename Container::Value & Middle(Container & container);