Почему C ++ STL настолько сильно зависит от шаблонов? (а не на * интерфейсах *)

Я имею в виду, помимо его обязательного имени (Стандартная библиотека шаблонов) …

Первоначально C ++ предназначался для представления концепций ООП на C. То есть: вы могли бы сказать, что конкретный объект мог и не мог сделать (независимо от того, как он это работает) на основе его иерархии classов и classов. Некоторые композиции способностей сложнее описать таким образом из-за проблемности множественного наследования и того факта, что C ++ поддерживает концепцию интерфейсов несколько неуклюжим способом (по сравнению с java и т. Д.), Но он есть (и может быть улучшен).

И затем в игру вошли шаблоны вместе с STL. STL, казалось, принял classические концепции ООП и сбросил их вниз, используя вместо этого шаблоны.

Должно быть различие между случаями, когда шаблоны используются для обобщения типов, где типы темы не имеют значения для работы шаблона (контейнеры, например). Наличие vector имеет смысл.

Однако во многих других случаях (iteratorы и алгоритмы) шаблонные типы должны следовать «концепции» (Input Iterator, Forward Iterator и т. Д.), Где фактические детали концепции полностью определяются реализацией шаблона function / class, а не classом типа, используемого с шаблоном, что несколько противоречит использованию ООП.

Например, вы можете указать функцию:

 void MyFunc(ForwardIterator *I); 

Обновление. Как было непонятно в исходном вопросе, ForwardIterator в порядке, чтобы сам был настроен, чтобы разрешить любой тип ForwardIterator. Наоборот, ForwardIterator является концепцией.

ожидает Forward Iterator, только взглянув на его определение, где вам нужно либо посмотреть на реализацию, либо на документацию:

 template  void MyFunc(Type *I); 

Две претензии, которые я могу сделать в пользу использования шаблонов: скомпилированный код можно сделать более эффективным, путем компиляции шаблона для каждого используемого типа, вместо использования vtables. И тот факт, что шаблоны могут использоваться с родными типами.

Тем не менее, я ищу более глубокую причину отказа от classического ООП в пользу шаблонов для STL? (Предполагая, что вы читаете так далеко: P)

Короткий ответ – «потому что C ++ перешел». Да, еще в конце 70-х, Stroustrup намеревался создать обновленный C с возможностями OOP, но это уже давно. К тому времени, когда язык был стандартизован в 1998 году, он больше не был языком ООП. Это был язык с несколькими парадигмами. Это, безусловно, имело некоторую поддержку кода ООП, но у него также был завершенный язык формулировки шаблонов, он позволил метапрограммировать время компиляции, и люди обнаружили общее программирование. Внезапно ООП просто не казался таким важным. Не тогда, когда мы можем писать более простой, более сжатый и более эффективный код, используя методы, доступные через шаблоны и общее программирование.

ООП не является святым Граалем. Это милая идея, и это было довольно улучшением по сравнению с процедурными языками еще в 70-х годах, когда оно было изобретено. Но это, честно говоря, не все, что было сломано. Во многих случаях он неуклюжий и многословный, и на самом деле он не способствует повторному использованию кода или модульности.

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

Попробуйте нарисовать график зависимостей гипотетического «ООП-ified» STL. Сколько classов должны знать друг о друге? Было бы много зависимостей. Могли бы вы включить только заголовок vector , не заставляя iterator или даже iostream ? STL делает это легко. Вектор знает о типе iteratorа, который он определяет, и все. Алгоритмы STL ничего не знают. Им даже не нужно включать заголовок iteratorа, даже если все они принимают iteratorы в качестве параметров. Какой из них более модульный?

STL не может следовать правилам ООП, как это определяет Java, но не достигает ли он целей ООП? Разве он не обеспечивает повторного использования, низкого сцепления, модульности и герметизации?

И не достигает ли он этих целей лучше, чем версия с ООП?

Что касается того, почему STL был принят на этот язык, произошло несколько вещей, которые привели к STL.

Сначала в C ++ были добавлены шаблоны. Они были добавлены по той же причине, что дженерики были добавлены в .NET. Казалось бы, неплохо было написать такие вещи, как «контейнеры типа Т», не отбрасывая безопасность типов. Конечно, реализация, на которой они остановились, была намного сложнее и мощнее.

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

Он представил его в комитет по языку C ++, которому потребовалось довольно много времени, чтобы привыкнуть к нему, потому что оно выглядело так странно и по-другому, но в конечном итоге осознало, что оно работает лучше, чем традиционные эквиваленты ООП, которые им пришлось бы включать в противном случае . Поэтому они внесли некоторые корректировки и приняли его в стандартную библиотеку.

Это был не идеологический выбор, это был не политический выбор «мы хотим быть ООПом или нет», а очень прагматичным. Они оценили библиотеку и увидели, что она работает очень хорошо.

В любом случае, обе причины, которые вы упомянули в пользу STL, абсолютно необходимы.

Стандартная библиотека C ++ должна быть эффективной. Если он менее эффективен, чем, скажем, эквивалентный ручной код C, люди не будут его использовать. Это снизит производительность, увеличит вероятность ошибок, и в целом просто будет плохой идеей.

И STL должен работать с примитивными типами, потому что примитивные типы – это все, что у вас есть на C, и они являются важной частью обоих языков. Если STL не работал с встроенными массивами, это было бы бесполезно .

У вашего вопроса есть сильное предположение, что ООП является «лучшим». Мне любопытно узнать, почему. Вы спрашиваете, почему они «отказались от classического ООП». Мне интересно, почему они должны были застрять в этом. Какие преимущества у него были бы?

Самый прямой ответ на то, о чем я думаю, вы спрашиваете / жалуетесь: это предположение о том, что C ++ является языком ООП, является ложным предположением.

C ++ – это язык с несколькими парадигмами. Он может быть запрограммирован с использованием принципов ООП, его можно запрограммировать процедурно, его можно запрограммировать в общих чертах (шаблоны), а с C ++ 11 (ранее известный как C ++ 0x) некоторые вещи можно даже запрограммировать функционально.

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

Я понимаю, что Stroustrup первоначально предпочитал дизайн контейнера в стиле OOP, и на самом деле не видел другого способа сделать это. Александр Степанов является ответственным за STL, и его цели не include «сделать его объектно-ориентированным» :

Это фундаментальный момент: алгоритмы определены на алгебраических структурах. Мне потребовалось еще пару лет, чтобы понять, что вы должны расширить понятие структуры, добавив требования сложности к регулярным аксиомам. … Я считаю, что теории iteratorов занимают центральное место в Computer Science, поскольку теории колец или банаховых пространств играют центральную роль в математике. Каждый раз, когда я смотрю на алгоритм, я бы попытался найти структуру, на которой она определена. Поэтому я хотел бы описать алгоритмы в целом. Это то, что я люблю делать. Я могу провести месяц, работая над известным алгоритмом, пытаясь найти его общее представление. …

STL, по крайней мере для меня, представляет собой единственный способ программирования. Это действительно отличается от программирования на C ++, поскольку оно было представлено и представлено в большинстве учебников. Но, видите ли, я не пытался программировать на C ++, я пытался найти правильный способ справиться с программным обеспечением. …

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

(Он объясняет, почему наследование и виртуальные игры, так называемый объектно-ориентированный дизайн, были «в корне ошибочны и не должны использоваться» в оставшейся части интервью).

Однажды Степанов представил свою библиотеку в Страуструп, Страуструп и другие пошли на геркулесовы усилия по его внедрению в стандарт ISO C ++ (то же интервью):

Поддержка Бьярне Страуструпа была исключительно важной. Бьярне действительно хотел STL в стандарте, и если Бьярне чего-то хочет, он это получит. … Он даже заставлял меня вносить изменения в STL, которые я никогда бы не сделал для кого-либо еще … он самый единомышленник, которого я знаю. Он все делает. Ему потребовалось некоторое время, чтобы понять, что такое STL, но когда он это сделал, он был готов проталкивать его. Он также внес свой вклад в STL, поддержав мнение о том, что более чем один способ программирования действителен – без каких-либо препятствий и шумихи в течение более десяти лет и сочетания гибкости, эффективности, перегрузки и безопасности типов в шаблоны, которые сделали STL возможным. Я хотел бы заявить, что Бьярн – выдающийся разработчик языков моего поколения.

Ответ найден в этом интервью со Степановым, автором STL:

Да. STL не является объектно-ориентированным. Я думаю, что объектная ориентация почти такая же обман, как и искусственный интеллект. Мне еще предстоит увидеть интересный fragment кода, который исходит от этих людей OO.

Почему бы лучше создать ООП-структуру библиотеки данных и алгоритмов?! ООП не является решением для каждой вещи.

IMHO, STL – самая элегантная библиотека, которую я когда-либо видел 🙂

для вашего вопроса,

вам не нужен polymorphism времени выполнения, для STL преимуществом является использование библиотеки с использованием статического polymorphismа, что означает эффективность. Попробуйте написать общий алгоритм сортировки или расстояния или любой алгоритм, который применяется ко всем контейнерам! ваш Сортировка в Java вызовет функции, которые являются динамическими с помощью n-уровней, которые будут выполняться!

Вам нужна глупая вещь, как Бокс и Unboxing, чтобы скрыть неприятные предположения о так называемых языках Pure OOP.

Единственная проблема, которую я вижу в STL, и шаблоны в целом – ужасные сообщения об ошибках. Что будет решаться с помощью Concepts в C ++ 0X.

Сравнение STL с коллекциями на Java похоже на сравнение Тадж-Махала с моим домом 🙂

шаблонные типы должны следовать «концепции» (Input Iterator, Forward Iterator и т. д.), где фактические детали концепции полностью определяются реализацией функции / classа шаблона, а не classом типа используется с шаблоном, что несколько снижает использование ООП.

Я думаю, вы неправильно понимаете предполагаемое использование понятий шаблонами. Форвардный iterator, например, является очень четкой концепцией. Чтобы найти выражения, которые должны быть действительными для того, чтобы class был Forward Iterator и их семантика, включая вычислительную сложность, вы смотрите на стандарт или на http://www.sgi.com/tech/stl/ForwardIterator.html (вы должны следить за ссылками на Input, Output и Trivial Iterator, чтобы увидеть все).

Этот документ является совершенно хорошим интерфейсом, и «фактические детали концепции» определены прямо там. Они не определяются реализацией форвардных iteratorов, и они не определяются алгоритмами, использующими форвардные iteratorы.

Различия в том, как интерфейсы обрабатываются между STL и Java, трижды:

1) STL определяет действительные выражения с использованием объекта, тогда как Java определяет методы, которые должны быть вызываемыми для объекта. Конечно, допустимым выражением может быть вызов метода (член-функции), но это необязательно.

2) Интерфейсы Java – это объекты времени исполнения, тогда как концепции STL не отображаются во время выполнения даже с RTTI.

3) Если вам не удалось выполнить допустимые допустимые выражения для концепции STL, вы получите неуказанную ошибку компиляции при создании экземпляра какого-либо шаблона с типом. Если вам не удалось реализовать требуемый метод интерфейса Java, вы получите конкретную ошибку компиляции, говоря об этом.

Эта третья часть – это если вам нравится (компиляция) «утиная печать»: интерфейсы могут быть неявными. В Java интерфейсы несколько явны: class «is» Iterable тогда и только тогда, когда он говорит, что он реализует Iterable. Компилятор может проверить, что подписи его методов все присутствуют и правильны, но семантика все еще неявная (т.е. они либо задокументированы, либо нет, но только больше кода (модульные тесты) могут рассказать вам, правильна ли реализация).

В C ++, как и в Python, семантика и синтаксис являются неявными, хотя в C ++ (и в Python, если вы получаете препроцессор с сильной типизацией) вы получаете некоторую помощь от компилятора. Если программисту требуется Java-подобное явное объявление интерфейсов с помощью classа-реализации, то стандартным подходом является использование признаков типа (и множественное наследование может помешать этому быть слишком многословным). Недостаток, по сравнению с Java, – это один шаблон, который я могу создать с помощью своего типа и который будет компилироваться, если и только если все необходимые выражения действительны для моего типа. Это скажет мне, выполнил ли я все необходимые бит, «прежде чем использовать его». Это удобство, но это не kernel ​​ООП (и оно по-прежнему не проверяет семантику, а код для проверки семантики, естественно, также проверяет правильность выражений, о которых идет речь).

STL может быть или не быть достаточно OO для вашего вкуса, но он, безусловно, отлично отделяет интерфейс от реализации. Он не обладает способностью Java отражать интерфейсы, и он сообщает о нарушениях требований интерфейса по-разному.

вы можете сказать, что функция … ожидает Forward Iterator, только взглянув на его определение, где вам нужно либо взглянуть на реализацию, либо на документацию для …

Лично я считаю, что неявные типы являются силой, когда они используются надлежащим образом. Алгоритм говорит, что он делает с его параметрами шаблона, и разработчик уверен, что все это работает: это именно общий знаменатель того, что нужно делать «интерфейсам». Кроме того, с STL вы вряд ли будете использовать, скажем, std::copy основываясь на поиске его прямого объявления в файле заголовка. Программисты должны разрабатывать, какую функцию выполняет на основе своей документации, а не только подписи функции. Это верно в C ++, Python или Java. Существуют ограничения на то, что может быть достигнуто при вводе текста на любом языке, и попытка использовать типизацию для выполнения чего-то, чего он не делает (проверьте семантику), будет ошибкой.

Тем не менее, алгоритмы STL обычно называют параметры своего шаблона таким образом, чтобы было ясно, какая концепция требуется. Однако это должно предоставить полезную дополнительную информацию в первой строке документации, а не делать декларации более информативными. Есть больше вещей, которые вам нужно знать, чем их можно инкапсулировать в типы параметров, поэтому вы должны прочитать документы. (Например, в алгоритмах, которые принимают входной диапазон и выходной iterator, скорее всего, выходному iteratorу требуется достаточно «пространства» для определенного количества выходов на основе размера входного диапазона и, возможно, значений в нем. )

Вот Bjarne на явно объявленных интерфейсах: http://www.artima.com/cppsource/cpp0xP.html

В generics аргумент должен быть classа, полученного из интерфейса (эквивалент C ++ для интерфейса – абстрактный class), указанный в определении родового. Это означает, что все общие типы аргументов должны вписываться в иерархию. Это накладывает ненужные ограничения на конструкции, требует неразумного предвидения со стороны разработчиков. Например, если вы пишете generic и я определяю class, люди не могут использовать мой class в качестве аргумента для вашего родословного, если я не знаю об указанном вами интерфейсе и не извлек из него свой class. Это жестко.

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

«ООП для меня означает только обмен сообщениями, локальное удержание и защиту и скрытие процесса состояния и крайне позднюю привязку всех вещей. Это можно сделать в Smalltalk и в LISP. Возможно, существуют другие системы, в которых это возможно, но Я не знаю о них. – Алан Кей, создатель Smalltalk.

C ++, Java и большинство других языков довольно далеки от classического ООП. Тем не менее, аргументирование идеологий не является ужасно продуктивным. C ++ не является чистым в любом смысле, поэтому он реализует функциональные возможности, которые, как представляется, в то время создают прагматический смысл.

Основная проблема с

 void MyFunc(ForwardIterator *I); 

как вы безопасно получаете тип вещи, которую возвращает iterator? С шаблонами это делается для вас во время компиляции.

STL начал свою работу с того, чтобы предоставить большую библиотеку, охватывающую наиболее часто используемый алгоритм, – с целью последовательного поведения и производительности . Шаблон стал ключевым фактором, позволяющим реализовать эту реализацию и достичь цели.

Просто чтобы предоставить другую ссылку:

Аль Стивенс Интервью Алекс Степанов, в марте 1995 года DDJ:

Степанов объяснил свой опыт работы и выбор в отношении большой библиотеки алгоритмов, которая в конечном итоге превратилась в STL.

Расскажите нам о своем долгосрочном интересе к родовому программированию

….. Затем мне предложили работу в Bell Laboratories, работающей в группе C ++ на C ++-библиотеках. Они спросили меня, могу ли я сделать это на C ++. Конечно, я не знал C ++ и, конечно, я сказал, что могу. Но я не мог сделать это на C ++, потому что в 1987 году на C ++ не было шаблонов, которые необходимы для включения этого стиля программирования. Наследование было единственным механизмом для получения общности, и этого было недостаточно.

Даже сейчас наследование C ++ не очень полезно для общего программирования. Давайте обсудим, почему. Многие люди пытались использовать наследование для реализации структур данных и classов контейнеров. Как мы знаем сейчас, было мало успешных попыток. Наследование C ++, а связанный с ним стиль программирования резко ограничен. Невозможно реализовать дизайн, который включает в себя тривиальную вещь, как использование равенства. Если вы начинаете с базового classа X в корне вашей иерархии и определяете оператор виртуального равенства в этом classе, который принимает аргумент типа X, то выведите class Y из classа X. Каков интерфейс равенства? Он имеет равенство, которое сравнивает Y с X. Используя животных в качестве примера (люди ОО любят животных), определите млекопитающих и выведите жирафа у млекопитающих. Затем определите член-функцию-член, где животные согласуются с животными и возвращают животное. Затем вы получаете жирафа от животного и, конечно же, у него есть помощник по функциям, где жираф соединяется с животным и возвращает животное. Это определенно не то, что вы хотите. Хотя спаривание может быть не очень важно для программистов на С ++, равенство есть. Я не знаю ни одного алгоритма, где не используется какое-либо равенство.

На мгновение давайте рассмотрим стандартную библиотеку как базу данных коллекций и алгоритмов.

Если вы изучили историю баз данных, вы, несомненно, знаете, что в начале базы данных были в основном «иерархическими». Иерархические базы данных очень близко относились к classическому ООП – в частности, к однонамерному многообразию, например, используемому Smalltalk.

Со временем стало очевидно, что иерархические базы данных могут использоваться для моделирования почти чего угодно, но в некоторых случаях модель с одним наследованием была довольно ограниченной. Если бы у вас была деревянная дверь, было бы удобно смотреть на нее либо как на дверь, либо на кусок какого-то сырья (сталь, дерево и т. Д.),

Таким образом, они изобрели базы данных сетевых моделей. Базы данных сетевых моделей очень тесно связаны с множественным наследованием. C ++ поддерживает множественное наследование полностью, в то время как Java поддерживает ограниченную форму (вы можете наследовать только один class, но также можете реализовать столько интерфейсов, сколько хотите).

Как иерархическая модель, так и базы данных сетевых моделей в основном исчезли из общего назначения (хотя некоторые из них остаются в довольно конкретных нишах). Для большинства целей они были заменены реляционными базами данных.

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

C ++ в значительной степени следовал одному и тому же пути. Совпадение между единичным наследованием и иерархической моделью, а также между множественным наследованием и сетевой моделью довольно очевидны. Соответствие между шаблонами C ++ и иерархической моделью может быть менее очевидным, но в любом случае это довольно близко подходит.

Я не видел формальных доказательств этого, но я считаю, что возможности шаблонов – это надмножество тех, которые предоставляются множественным наследованием (что явно является дополнением к единому inerhitance). Одна сложная часть заключается в том, что шаблоны в основном статически связаны – то есть все привязки происходят во время компиляции, а не во время выполнения. Таким образом, формальное доказательство того, что наследование обеспечивает надмножество возможностей наследования, может быть несколько сложным и сложным (или даже может быть невозможным).

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

Как вы делаете сравнения с ForwardIterator *? То есть, как вы проверяете, есть ли у вас предмет, что вы ищете, или вы его передали?

В большинстве случаев я бы использовал что-то вроде этого:

 void MyFunc(ForwardIterator& i) 

что означает, что я знаю, что я указываю на MyType, и я знаю, как их сравнивать. Хотя это выглядит как шаблон, на самом деле это не так (ключевое слово «шаблон»).

У этого вопроса много замечательных ответов. Следует также отметить, что шаблоны поддерживают открытый дизайн. При текущем состоянии объектно-ориентированных языков программирования при работе с такими проблемами необходимо использовать шаблон посетителя, а истинный ООП должен поддерживать несколько динамических привязок. См. Раздел « Открытые мульти-методы для C ++», P. Pirkelbauer, et.al. для очень интересного чтения.

Еще одна интересная точка шаблонов заключается в том, что они могут использоваться для polymorphismа времени выполнения. Например

 template Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func) { auto dt=(t_end-t_0)/N; for(size_t k=0;k 

Обратите внимание, что эта функция также будет работать, если Value является вектором какого-либо типа (а не std :: vector, который должен быть вызван std::dynamic_array чтобы избежать путаницы)

Если func мал, эта функция получит много преимуществ от вложения. Пример использования

 auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y) {return y;}); 

В этом случае вам следует знать точный ответ (2.718 ...), но легко создать простой ODE без элементарного решения (подсказка: используйте полином в y).

Теперь у вас есть большое выражение в func , и вы используете решатель ODE во многих местах, поэтому ваш исполняемый файл повсеместно загрязняется с помощью экземпляров шаблонов. Что делать? Первое, что нужно заметить, это то, что работает обычный указатель функции. Затем вы хотите добавить currying, чтобы вы писали интерфейс и явное инстанцирование

 class OdeFunction { public: virtual double operator()(double t,double y) const=0; }; template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func); 

Но описанное выше действие работает только в double , почему бы не написать интерфейс в качестве шаблона:

 template class OdeFunction { public: virtual Value operator()(double t,const Value& y) const=0; }; 

и специализируются на некоторых общих типах значений:

 template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func); template vec4_t euler_fwd(size_t N,double t_0,double t_end,vec4_t y_0,const OdeFunction< vec4_t >& func); // (Native AVX vector with four components) template vec8_t euler_fwd(size_t N,double t_0,double t_end,vec8_t y_0,const OdeFunction< vec8_t >& func); // (Native AVX vector with 8 components) template Vector euler_fwd(size_t N,double t_0,double t_end,Vector y_0,const OdeFunction< Vector >& func); // (A N-dimensional real vector, *not* `std::vector`, see above) 

Если функция сначала была спроектирована вокруг интерфейса, тогда вам пришлось бы наследовать эту ABC. Теперь у вас есть этот параметр, а также указатель на функцию, lambda или любой другой объект функции. The key here is that we must have operator()() , and we must be able to do use some arithmetic operators on its return type. Thus, the template machinery would break in this case if C++ did not have operator overloading.

The concept of separating interface from interface and being able to swap out the implementations is not intrinsic to Object-Oriented Programming. I believe it’s an idea that was hatched in Component-Based Development like Microsoft COM. (See my answer on What is Component-Driven Development?) Growing up and learning C++, people were hyped out inheritance and polymorphism. It wasn’t until 90s people started to say “Program to an ‘interface’, not an ‘implementation'” and “Favor ‘object composition’ over ‘class inheritance’.” (both of which quoted from GoF by the way).

Then Java came along with built-in garbage collector and interface keyword, and all of a sudden it became practical to actually separate interface and implementation. Before you know it the idea became part of the OO. C++, templates, and STL predates all of this.

  • static_assert зависит от параметра шаблона непигового типа (различное поведение на gcc и clang)
  • Выполнение static_assert, что тип шаблона является другим шаблоном
  • Interesting Posts

    Невозможно установить заголовок в JSP. Ответ уже сделан

    Найти текстовую строку с помощью jQuery?

    401 код ответа для запросов json с ASP.NET MVC

    Добавьте ведущие нули / 0 в существующие значения Excel на определенную длину

    Последующий удаленный nohup с tcsh

    Почему длина возвращает 1 для кортежа с 2 элементами и дает ошибку для кортежа с большим количеством элементов?

    Как оживить изменение изображения в UIImageView?

    Создание UUID из строки без тире

    Данные на корневом уровне недействительны. Строка 1, позиция 1 – почему я получаю эту ошибку при загрузке xml-файла?

    Как преобразовать DataTable в общий список?

    Каков всеобъемлющий диапазон float и double в Java?

    Значок подсчета уведомлений панели действий (значок), например, Google

    В OpenCV 3.0 отсутствует несвободный модуль

    Файлы Rsync через промежуточный хост

    Проблема воссоздания BCD на Windows 7 64bit – запрошенное системное устройство не может быть найдено

    Давайте будем гением компьютера.