Чем отличаются ассоциативные типы от обобщенных типов и шаблонов?
Чем отличаются ассоциативные типы (Associated Types) от обобщенных типов (Generics Types) и шаблонов (Templates)?
Я не понимаю разницы между этими понятиями, кроме как шаблонов, хотя они похоже. Кто может объяснить с примерами когда и что лучше использовать? Чем один вариант лучше другого? На примере Rust, Swift и C++, если возможно.
Ответы (1 шт):
Основное отличие обобщений 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
{
//...
}
}
}
При объявлении нового шаблона происходит только базовая проверка синтаксиса, а пригодность параметров к тем действиям, которые с ними будут производиться, определяется только при создании экземпляра шаблона. В С++ в теле шаблона можно писать практически что угодно, а с тем, имеет ли это какой-то смысл, компилятор будет в каждом конкретном случае разбираться потом, только когда шаблон будет где-то использован.
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); // и только тут компилятор выдаст вереницу ошибок
}
В С++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
}
Причем я бы хотел упомянуть такой ньюанс, что возможности писать в концепте просто -> 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);