Какие еще языки поддерживают «частичную специализацию»?

Частичная специализация шаблонов - одна из важнейших концепций универсального программирования на C++. Например: для реализации общей функции подкачки:

template <typename T>
void swap(T &x, T &y) {
  const T tmp = x;
  y = x;
  x = tmp;
}

Чтобы специализировать его для вектора для поддержки обмена O (1):

template <typename T, class Alloc>
void swap(vector<T, Alloc> &x, vector<T, Alloc> &y) { x.swap(y); }

Таким образом, вы всегда можете получить оптимальную производительность, когда вызываете swap (x, y) в универсальной функции;

Приветствую вас, если вы можете опубликовать эквивалент (или канонический пример частичной специализации языка, если язык не поддерживает концепцию подкачки) на альтернативных языках.

РЕДАКТИРОВАТЬ: похоже, что многие люди, которые ответили / прокомментировали, действительно не знают, что такое частичная специализация, и что общий пример обмена, похоже, мешает пониманию некоторыми людьми. Более общий пример:

template <typename T>
void foo(T x) { generic_foo(x); }

Частичная специализация будет:

template <typename T>
void foo(vector<T> x) { partially_specialized_algo_for_vector(x); }

Полная специализация будет:

void foo(vector<bool> bitmap) { special_algo_for_bitmap(bitmap); }

Почему это важно? потому что вы можете вызвать foo (что угодно) в общей функции:

template <typename T>
void bar(T x) {
  // stuff...
  foo(x);
  // more stuff...
}

и получите наиболее подходящую реализацию во время компиляции. Это один из способов для C++ добиться абстракции с минимальным снижением производительности.

Надеюсь, это поможет прояснить понятие «частичная специализация». В некотором смысле, именно так C++ выполняет сопоставление с образцом типов без необходимости явного синтаксиса сопоставления с образцом (скажем, ключевого слова match в Ocaml / F#), что иногда мешает универсальному программированию.

Боюсь, что ни одна из перечисленных вами функций не может считаться частичной специализацией, поскольку вы не можете частично специализировать шаблонные функции, а только классы. Честно говоря, это возникает только при рассмотрении попытки расширить пространство имен std для определяемых пользователем шаблонных типов. (Вам разрешено специализироваться, но не перегружать материал в std.) См. Эффективный элемент C++ 25 или эту статью GotW gotw.ca/publications/mill17.htm. Отбросьте это, и это просто вопрос о правилах перегрузки функций и поиска.

AFoglia 11.08.2009 19:47
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
11
1
623
6

Ответы 6

В Java есть дженерики, которые позволяют делать подобные вещи.

Извините, дженерики Java уродливы и даже близко не подходят для чего-то подобного.

obecalp 19.12.2008 10:46

ну, в java вы можете реализовать своп за время O (1) для любого общего класса.

Milhous 19.12.2008 20:46

C#:

void Swap<T>(ref T a, ref T b) {   
  var c = a;   
  a = b;   
  b = c;
}

Я предполагаю, что (чистая) версия Haskell будет:

swap :: a -> b -> (b,a)
swap a b = (b, a)

Но здесь нет частичной специализации.

Alexey Romanov 19.12.2008 11:10

Своп не имеет смысла в Haskell, потому что переменные неизменяемы. Можете ли вы привести еще один пример, который имеет смысл на других языках? Общий своп в Lisp будет использовать макрос: (defmacro swap (x y) `(let ((z, x)) (setf, x, y) (setf, y, z)))

Jules 19.12.2008 11:32

Боюсь, что C# не поддерживает частичную специализацию шаблонов.

Частичная специализация шаблона означает:

У вас есть базовый класс с двумя или более шаблонами (общие / типовые параметры). Параметры типа будут <T, S>

В производном (специализированном) классе вы указываете тип одного из параметров типа. Параметры типа могут выглядеть так: <T, int>.

Поэтому, когда кто-то использует (создает экземпляр объекта) класса, в котором последним параметром типа является int, используется производный класс.

Не могли бы вы ограничить S в производном классе, указав где S: int?

Jon Limjap 19.12.2008 11:13

Нет; вам нужно сделать так, чтобы базовый класс времени любой создавался с помощью S == int, вместо этого создается экземпляр производного класса.

Alexey Romanov 19.12.2008 11:18

В Perl это просто ($ x, $ y) = ($ y, $ x);

Brad Gilbert 20.12.2008 01:06

В Haskell есть перекрывающиеся экземпляры в качестве расширения:

class Sizable a where
  size :: a -> Int

instance Collection c => Sizable c where
  size = length . toList

это функция для определения размера любой коллекции, которая может иметь более конкретные экземпляры:

instance Sizable (Seq a) where
  size = Seq.length

См. Также Расширенное перекрытие на HaskellWiki.

Собственно, вы можете (не совсем; см. Ниже) сделать это на C# с помощью методов расширения:

public Count (this IEnumerable<T> seq) {
    int n = 0;
    foreach (T t in seq)
        n++;
    return n;
}

public Count (this T[] arr) {
    return arr.Length;
}

Тогда при вызове array.Count() будет использоваться специализированная версия. «Не совсем», потому что разрешение зависит от статического типа array, а не от типа времени выполнения. Т.е. это будет использовать более общую версию:

IEnumerable<int> array = SomethingThatReturnsAnArray();
return array.Count();

Версия C++ также решается во время компиляции, так что это не так уж и далеко. Проблема начинается, когда вы вызываете Count из общего класса, а компилятор не знает, для какого типа вы его вызываете. C++ здесь работает лучше, потому что он откладывает решение до тех пор, пока не узнает все типы.

Daniel Earwicker 23.12.2008 14:04

D поддерживает частичную специализацию:

(просканируйте "частичное" в приведенных выше ссылках).

Вторая ссылка, в частности, предоставит вам подробное описание очень того, что вы можете делать со специализацией шаблонов, не только в D, но и в C++.

Вот конкретный пример swap D. Он должен распечатать сообщение для свопа, специализированного для класса Thing.

import std.stdio;    // for writefln

// Class with swap method

class Thing(T)
{
public:

    this(T thing)
    {
        this.thing = thing;
    }

    // Implementation is the same as generic swap, but it will be called instead.
    void swap(Thing that)
    {
       const T tmp = this.thing;
       this.thing = that.thing;
       that.thing = tmp;
    }

public:

    T thing;
}


// Swap generic function

void swap(T)(ref T lhs, ref T rhs)
{
    writefln("Generic swap.");

    const T tmp = lhs;
    lhs = rhs;
    rhs = tmp;
}

void swap(T : Thing!(U))(ref T lhs, ref T rhs)
{
    writefln("Specialized swap method for Things.");

    lhs.swap(rhs);
}

// Test case

int main()
{
    auto v1 = new Thing!(int)(10);
    auto v2 = new Thing!(int)(20);

    assert (v1.thing == 10);
    assert (v2.thing == 20);
    swap(v1, v2);
    assert (v1.thing == 20);
    assert (v2.thing == 10);

    return 0;
}

Да, D определенно поддерживает это. Спасибо за пример. Кстати, вы не думаете, что swap (T: Thing! (U), U) (...) действительно должен быть swap (T: Thing! (U)) (...). Дополнительная буква «U» кажется неуместной.

obecalp 24.12.2008 23:32

Да, ты прав. Я вырезал и вставил его из исходного файла, прежде чем моя компиляция была завершена (состояние гонки в реальном мире: P). Фиксированный.

quark 25.12.2008 00:35

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