Что отличает шаблон от общего?
Я понимаю аспекты шаблонов в С++, которые отличаются от дженериков в Java и С#. С# - это reification, Java использует стирание типов, С++ использует утиную печать и т.д. Существует множество вещей, которые могут создавать шаблоны С++, которые не могут использовать Java и С# generics (например, специализация шаблона). Но есть нескольких вещей, которые генерируют дженерики Java, которые С# и С++ не могут (например, создать параметр ограниченного типа семейства дженериков типа class Foo<T extends Comparable<?>>
) и ряд вещей, которые генерируют С# может сделать то, что Java и С++ не могут (например, общее представление времени исполнения). [EDIT: По-видимому, Java-дженерики намного слабее, чем я думал. (Что-то говорит.) В любом случае, несмотря на их неумелость, они по-прежнему считаются дженериками вместе с генераторами С#.]
Я не понимаю, что концептуально делает шаблоны отличными от дженериков. Какие части шаблонов на С++ - это вещи, которые нельзя сделать в том, что не является шаблоном, но является общим? Например, если бы я должен был реализовать язык, поддерживающий шаблоны, что бы в нем было необходимо? Что я мог бы оставить в стороне, что было бы необходимо, чтобы язык поддерживал дженерики?
Я предполагаю, что шаблоны - это супер-набор дженериков, или они являются способом реализации дженериков, но я действительно не понимаю, что отделяет истинный шаблон от истинного общего.
Ответы
Ответ 1
Hm.. Если вы говорите, что понимаете глубинные шаблоны С++ и говорите, что не видите/не ощущаете разницу между дженериками и ими, ну, скорее всего, вы правы:)
Есть много различий, которые описывают, как/почему генерики лучше, чем шаблоны, перечисляют тонны различий и т.д., но это в основном не имеет отношения к сути идеи.
Идея состоит в том, чтобы обеспечить лучшее повторное использование кода. Шаблоны/дженерики предоставляют вам способ создания определений классов более высокого порядка, которые абстрактны по некоторым фактическим типам.
В этих терминах нет никакой разницы между ними, и единственными отличиями являются те, которые применяются конкретными функциями и ограничениями основного языка и среды выполнения.
Можно утверждать, что generics предоставляют некоторые дополнительные функции (обычно, когда речь идет о динамической интроспекции дерева классов объектов), но очень немногие из них (если вообще есть) не могут быть реализованы вручную в шаблонах С++, С некоторыми усилиями большинство из них могут быть реализованы или эмулированы, следовательно, они не хороши как различие между "правильными дженериками" и "реальными шаблонами".
Другие будут утверждать, что единственная потенциальная сила оптимизации, доступная благодаря поведению C-2, является разницей. Извините, неправда. JIT в Java и С# тоже могут это сделать, ну, почти, но делайте это очень хорошо.
Есть, однако, одна вещь, которая действительно может сделать Java/С# generics истинным подмножеством функций шаблонов С++. И вы даже упомянули об этом!
Это специализация шаблона.
В С++ каждая специализация ведет себя как совершенно другое определение.
В С++ template<typename T> Foo
, специализированный для T == int, может выглядеть так:
class Foo<int>
{
void hug_me();
int hugs_count() const;
}
в то время как "тот же" шаблон, специализированный для T == MyNumericType, может выглядеть как
class Foo<MyNumericType>
{
void hug_me();
MyNumericType get_value() const;
void reset_value() const;
}
FYI: это просто псевдокод, не будет компилироваться:)
Ни Java, ни С# generics не могут это сделать, потому что в их определении указано, что все универсальные типы-материализации будут иметь один и тот же "пользовательский интерфейс".
Более того, С++ использует правило SFINAE. Для шаблона могут существовать многие "теоретически сталкивающиеся" определения специализаций. Однако, когда используется шаблон, используются только те, которые "действительно хороши".
С классами, аналогичными приведенному выше примеру, если вы используете:
Foo<double> foood;
foood.reset_value();
будет использоваться только вторая специализация, так как первая не будет компилироваться из-за отсутствия "reset_value".
С помощью дженериков вы не можете этого сделать. Вам нужно будет создать общий класс, который имеет все возможные методы, а затем во время выполнения динамически проверяет внутренние объекты и бросает некоторые "не реализованные" или "не поддерживаемые" исключения для недоступных методов. Это... просто ужасно. Такие вещи должны быть возможны во время компиляции.
Фактическая мощность, последствия, проблемы и общая сложность специализации шаблонов и SFINAE - это то, что действительно отличает дженерики и шаблоны. Просто, дженерики определяются таким образом, что специализация невозможна, поэтому SFINAE невозможно, следовательно, весь механизм парадоксально намного проще/проще.
Оба проще и проще реализовать в внутренних компонентах компилятора и пониматься с помощью неразумных мозгов.
Хотя я согласен с общими преимуществами генериков в Java/С#, я действительно скучаю по специализациям, гибкости интерфейса и правилу SFINAE. Тем не менее, я не был бы справедлив, если бы не упомянул одну важную вещь, связанную с разумным дизайном OO: если вы специализируетесь на шаблоне xxx, фактически меняете его клиентский API, то, скорее всего, его следует назвать по-разному и должны образовывать другой шаблон, Все дополнительные лакомства, которые могут создавать шаблоны, были в основном добавлены в набор инструментов, потому что... на С++ не было никакого отражения, и его нужно каким-то образом эмулировать. SFINAE - это форма отражения во времени компиляции.
Следовательно, самый большой игрок в мире различий уменьшается до любопытного (полезного) побочного эффекта исправления, применяемого для маскировки дефицита времени выполнения, что является почти полным отсутствием интроспекции времени выполнения:))
Поэтому я говорю, что нет никакой разницы, кроме каких-либо произвольных, принудительно выполняемых laguage, или каких-либо произвольных, применяемых платформой времени выполнения.
Все они всего лишь форма классов или функций более высокого порядка, и я думаю, что это самая важная вещь и функция.
Ответ 2
Во-первых, мне интересно, что RTTI/introspection является большой частью большинства ответов. Ну, это не разница между дженериками и шаблонами, а скорее языками с instrospection и языками, которые его не имеют. В противном случае вы можете также утверждать, что это разница в классах С++ с Java-классами, а функции С++ с функциями Java...
Если вы отвлеките интроспекцию, основное отличие состоит в том, что шаблоны определяют полный язык обучения, функциональный по стилю, хотя с ужасной грамматикой, на которой вы можете программировать. Первый действительно сложный пример, который я слышал (я бы хотел иметь код, но я не знаю) был программой, которая вычисляла простые числа во время компиляции. Это имеет другое значение: шаблоны могут принимать аргументы типа или аргументы шаблона или несимвольные аргументы (не-тип относится ко всему, что не является типом или шаблоном, например значением int
).
Это было упомянуто в других ответах, но просто сказал, что шаблоны могут быть специализированными и что в SFINAE четко не указано, что эти две функции достаточны для создания полного языка обучения.
Ответ 3
нет, шаблоны не являются супермножеством генериков, а на шаблонах С++ у вас нет поддержки во время выполнения на том же уровне, что и у вас с С# generics, что означает, что RTTI в С++ не может обнаружить и предоставить вам метаданные шаблонов, таких как Отражение выполняется для дженериков в С#.
рядом с этим мне нравится этот фрагмент:
В шаблонах С++ используется модель времени компиляции. Когда шаблон используется в С++, эффект выглядит так, как если бы сложный макропроцессор.
С# generics - это не только функция компилятора, но и функция времени выполнения. Общий тип, такой как List, поддерживает его родовую (родовую) после того, как она была скомпилирована. Или, чтобы посмотреть на это по-другому, подстановка, которую компилятор С++ делает при компиляции время выполняется во время JIT в общем мире С#.
см. здесь для полной статьи: Как обобщить С# с шаблонами С++?
Ответ 4
существует ряд вещей, которые генерируют дженерики Java, которые С# и С++ не могут (например, сделать параметр ограниченного типа семейства дженериков типа class Foo<T extends Comparable<?>>
)
Не совсем верно для этого примера:
template <typename Comparable>
struct Foo {
static bool compare(const Comparable &lhs, const Comparable &rhs) {
return lhs == rhs;
}
};
Этот шаблон шаблона успешно создаст экземпляр функции compare
только в том случае, если параметр шаблона является сопоставимым с равенством типом. Он не называется "параметром ограниченного типа", но он служит той же цели.
Если в С++ вы хотите рассматривать Comparable
как явный интерфейс (т.е. базовый класс), а не концепцию с утиным типом, вы можете static_assert(is_base_of<Comparable, T>::value, "objects not Comparable");
или что-то еще.
Ответ 5
Я ограничу свои ответы на шаблоны С++ и дженериков Java.
- С++ шаблоны (шаблоны шаблонов и шаблонов функций) - это механизмы для
реализация полиморфизма времени компиляции, но генераторы Java AFAIK - это время выполнения
механизмы.
- используя шаблоны С++, вы можете выполнять общее программирование, и это на самом деле
это полностью отдельный стиль программирования/парадигма, но Java-дженерики - это OO
стиль как таковой. см. ниже:
-
Шаблоны С++ основаны на типизации Duck, но Java generics основаны на
Тип Erasure. В С++, vector<int>
, vector<Shape *>
, vector<double>
и
vector<vector<Matrix>>
- 4 различных типа, но в Java Cell<int>
, Cell<Integer>
Cell<double>
и Cell<Matrix>
являются одинаковыми. Точнее, во время генерации кода
компилятор стирает тип на первом месте.
Вы можете проверить это, выполнив следующий код из следующего документа:
Владимир Батов. Шаблоны Java и шаблоны С++. Журнал пользователей C/С++, июль 2004 г.
public class Cell<E>
{
private E elem;
public Cell(E elem)
{
this.elem = elem;
}
public E GetElement()
{
return elem;
}
public void SetElement(E elem)
{
this.elem = elem;
}
}
boolean test()
{
Cell<Integer> IntCell(10);
Cell<String> StrCell("Hello");
return IntCell.getClass() ==
StrCell.getClass(); // returns true
}
Короче говоря, Java претендует на универсальность, в то время как С++ на самом деле.