Что отличает шаблон от общего? - программирование
Подтвердить что ты не робот

Что отличает шаблон от общего?

Я понимаю аспекты шаблонов в С++, которые отличаются от дженериков в Java и С#. С# - это reification, Java использует стирание типов, С++ использует утиную печать и т.д. Существует множество вещей, которые могут создавать шаблоны С++, которые не могут использовать Java и С# generics (например, специализация шаблона). Но есть нескольких вещей, которые генерируют дженерики Java, которые С# и С++ не могут (например, создать параметр ограниченного типа семейства дженериков типа class Foo<T extends Comparable<?>>) и ряд вещей, которые генерируют С# может сделать то, что Java и С++ не могут (например, общее представление времени исполнения). [EDIT: По-видимому, Java-дженерики намного слабее, чем я думал. (Что-то говорит.) В любом случае, несмотря на их неумелость, они по-прежнему считаются дженериками вместе с генераторами С#.]

Я не понимаю, что концептуально делает шаблоны отличными от дженериков. Какие части шаблонов на С++ - это вещи, которые нельзя сделать в том, что не является шаблоном, но является общим? Например, если бы я должен был реализовать язык, поддерживающий шаблоны, что бы в нем было необходимо? Что я мог бы оставить в стороне, что было бы необходимо, чтобы язык поддерживал дженерики?

Я предполагаю, что шаблоны - это супер-набор дженериков, или они являются способом реализации дженериков, но я действительно не понимаю, что отделяет истинный шаблон от истинного общего.

4b9b3361

Ответ 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 претендует на универсальность, в то время как С++ на самом деле.