Чем отличаются ассоциативные типы от обобщенных типов и шаблонов?

Рейтинг: 0Ответов: 1Опубликовано: 22.04.2023

Чем отличаются ассоциативные типы (Associated Types) от обобщенных типов (Generics Types) и шаблонов (Templates)? Я не понимаю разницы между этими понятиями, кроме как шаблонов, хотя они похоже. Кто может объяснить с примерами когда и что лучше использовать? Чем один вариант лучше другого? На примере Rust, Swift и C++, если возможно.

Ответы

▲ 3Принят

Основное отличие обобщений 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);