Pull to refresh

Comments 183

Мда… И потом они ругают Python, PHP, Node за странности.
Правильно ругают. C++ — язык с массой странностей, которые возникают из принципа «платите только за то, что используете», который требуется для написания быстрого и эффективного (по памяти) кода. Всё, что описано в статье, в общем, отсюда.

Но с какого перепугу в языках, которые жрут память как не в себя и работают со скоростью черепахи куча закидонов?
Не нужно путать теплое с мягким. Речь не о потреблении памяти и скорости. Речь о странностях самого языка. Они есть везде фактически. Поэтому вместо того, чтобы искать козла отпущения, нужно заниматься исправлением последних в своем любимом языке.
UFO just landed and posted this here
Мне кажется, исправить странности в C++ уже в принципе невозможно. Причина странностей — сохранение обратной совместимости при постоянном навешивании нового функционала. Это приводит к появлению нелогичных и многословных конструкций.
Нет, в данном случае именно принцип «платите за то что используете». У разработчиков C++ были такие понятия что инициализация занимает время и жрет ресурсы. Зачем вам инициализация в 0 для всех целых чисел по умолчанию вы ведь наверняка собираетесь присвоить им какие-то другие значения?

Например в Java, которую разрабочики тогда противопоставляли именно C++ сразу же был сделан противоположный выбор)))

Инициализировано всегда и все)))

Второй выбор, сделанный с той же целью, со сборкой мусора вместо ручного управления памятью более известен публике )
Нет, в данном случае именно принцип «платите за то что используете». У разработчиков C++ были такие понятия что инициализация занимает время и жрет ресурсы. Зачем вам инициализация в 0 для всех целых чисел по умолчанию вы ведь наверняка собираетесь присвоить им какие-то другие значения?

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


Для примера, в некоторых современных языках есть три способа создать переменную: проициниализировать рекурсивно все поля руками, вызвать mem::unitialized() или mem::zeroed(). Вроде как покрывает все потребности.


Не говоря про то, что даже эти способы деприкейтят в пользу MaybeUninit, потому что с ними легко отстрелить себе ногу. Инициализация без вызова какого-либо конструктора есть конструирование объекта в невалидном состоянии. То есть уб, по сути.

Никто же не предлагает доработать алгол, чтобы писать на нем современные сайты?
Вот именно поэтому алгол и его потомки и сошли со сцены. Почему в 80е все писали на Паскале (кто-то на СМ'ках, кто-то на PC, да даже и Mac — тоже изначально был на диалекте Паскаля написан), а уже в 90е — перестали? Во-многом — именно потому, что Модула-2/Оберон и так далее не позволяли использовать сущесвующий код, а C++ — позволял.

Не говоря про то, что даже эти способы деприкейтят в пользу MaybeUninit, потому что с ними легко отстрелить себе ногу. Инициализация без вызова какого-либо конструктора есть конструирование объекта в невалидном состоянии. То есть уб, по сути.
Ну вот и посмотрим как это всё произойдёт. Устроят тот же бардак, что Вирт с сотоварищами с потомками Алгола устроил — займут ту же нишу. Важных языков, про которые вы можете почитать в книжке «история Языков Программирования».

Нет, тут именно что навешено 100500 культурных слоев.
Увы, но иначе — никак. Либо у вас 100500 культурных слоёв, либо вы — странчика в википедии.
Вот именно поэтому алгол и его потомки и сошли со сцены. Почему в 80е все писали на Паскале (кто-то на СМ'ках, кто-то на PC, да даже и Mac — тоже изначально был на диалекте Паскаля написан), а уже в 90е — перестали? Во-многом — именно потому, что Модула-2/Оберон и так далее не позволяли использовать сущесвующий код, а C++ — позволял.

Pascal->Delphi->C# живет и здравствует, однако. Кто куда сошел со сцены — непонятно…

Увы, но иначе — никак. Либо у вас 100500 культурных слоёв, либо вы — странчика в википедии.

Citation needed.

Вы так уверены, что через 1000 лет мы будем продолжать С++ пользоваться?
Pascal->Delphi->C# живет и здравствует, однако. Кто куда сошел со сцены — непонятно…
Вот только не надо сову на глобус натягивать, ей больно. Классическая линейка это Algol→Pascal→Modula-2→Oberon→Oberon 2→забвение.

Delphi — это как раз попытка развития в духе «C/C++» (относительно успешная до момента, когда кто-то решил, что он умнее всех и сломал совместимость), но вот C# сюда вообще никаким боком не относится, это потомок совсем другого проекта (грубо говоря «Java с блекджеком и шлюхами», родившаяся из судебного решения). Ada — ещё чуть-чуть как-то где-то, но никак не C#, извините.

Вы так уверены, что через 1000 лет мы будем продолжать С++ пользоваться?
Нет, конечно. Время от времени замена происходила и будет проиходить. Но далеко не каждое десятилетие. Тот же C# за то время, которое потребовалось, чтобы потеснить C++ пережил массу изменений и в нём уже полно легаси — но если попытаться вот это вот всё выкинуть, то люди с него уйдут.

Да, со временем, если у вас много-много денег и терпения, лет за 10-15-20 — вы, может быть, и сможете новых разработчиков привлечь (как произошло с VB.NET, который вроде как все похоронили, списали со счетов… а он-таки начал набирать популярность лет 5 назад)… но только если вы будете эти 10-15-20 обеспечивать совместимость. Не будете, будете играть в те игры, что и Вирт… ну получите то, что получите.
Тот же C# за то время, которое потребовалось, чтобы потеснить C++ пережил массу изменений и в нём уже полно легаси — но если попытаться вот это вот всё выкинуть, то люди с него уйдут.

В отличие от C++, у C# (да и Java) есть огромное преимущество — компиляция в MSIL/байт-код и полная совместимость на уровне сборок. Это даёт возможность для развития многочисленных надстроек типа Kotlin, F# и т.д.

и полная совместимость на уровне сборок.

У чего есть обратная сторона медали: те же генерики к Java прикручивали сбоку изолентой и получилось ну так себе.

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


Кстати, в C#, когда в качестве дженерик-параметров используются только ссылочные типы, дженерики реализуются аналогично Java.


Та же Microsoft учла эти ошибки и реализовала абсолютно нормальный CIL ( https://stackoverflow.com/questions/95163/differences-between-msil-and-java-bytecode ). И он оказался настолько удачным, что я помню, чтобы его хоть как-то дорабатывали.

> Проблема в Java не столько в кривых дженериках, сколько в отсутствии типов-структур

Что-то вас мотает из стороны в сторону. В JVM ничего не ломали, поэтому при реализации генериков пришлось прибегнуть к type erasure.

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

Только вот растущая популярность Go показывает, что наличие промежуточных представлений и тяжелого run-time — это недостаток для определенных ниш.
Что-то вас мотает из стороны в сторону. В JVM ничего не ломали, поэтому при реализации генериков пришлось прибегнуть к type erasure.

Нет. Я просто объяснил причину, почему дженерики в Java были реализованы именно как чисто языковая фишка без поддержки со стороны JVM.


Только вот растущая популярность Go показывает, что наличие промежуточных представлений и тяжелого run-time — это недостаток для определенных ниш.

И именно поэтому MS стала развивать отдельную ветку .NET Core.

> Я просто объяснил причину, почему дженерики в Java были реализованы именно как чисто языковая фишка без поддержки со стороны JVM.

Да? И как бы наличие структур помогло бы избежать type erasure?

> И именно поэтому MS стала развивать отдельную ветку .NET Core.

Ну да, ну да.
Да? И как бы наличие структур помогло бы избежать type erasure?

Наличие структур заставило бы искать другие пути решения проблемы. Потому что иначе пришлось бы либо мириться с боксингом структур в объекты.

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

Как наличие структур могло бы помощь избежать type erasure и, при этом, сохранить совместимость на уровне байт-кода?

А в чём проблема совместимости на уровне байт-кода? Насколько я знаю, он уже несколько раз дорабатывался.

Как наличие структур могло бы помощь избежать type erasure и, при этом, сохранить совместимость на уровне байт-кода?

Зачем сохранять прямую совместимость на уровне байт-кода? Достаточно просто расширить байт-код в очередной из версий JVM, не нарушив принципы обратной совместимости (работоспособность старых приложений в новых версиях среды). А о прямой совместимости (работа новых приложений в старых средах) речи никогда и не шло.


Например, Microsoft в своё время так и сделала.

Зачем сохранять прямую совместимость на уровне байт-кода?

Да без этого вот эти ваши слова:


В отличие от C++, у C# (да и Java) есть огромное преимущество — компиляция в MSIL/байт-код и полная совместимость на уровне сборок.

просто превращаются в тыкву. Поскольку если нет совместимости на уровне сборок или class-файлов, то о каком тогда преимуществе может идти речь?

Как это нет? Обратная совместимость на уровне сборок есть и никуда не девалась. Код, написанный под старый фреймворк, будет работать и на новых фреймворках.

Соответственно, если в старом class-файле какой-нибудь метод ждет просто ArrayList, а из нового class-файла ему подсовывают ArrayList<MyType>, то что?

То же самое, что бывает когда старый метод ждет какой-нибудь Vector, а ему передают ArrayList.

То будет ошибка компиляции. Это два разных типа. Видимо, создатель старой библиотеки плохо подумал и вместо следования принципам SOLID вместо интерфейса List принимает на вход конкретный тип.


Например, в том же C# оба класса — и ArrayList и List<> реализуют не-дженерик интерфейс IList.

Ну а какая разница? Старый код ожидает List (который работает с Object-ом), а в новом уже List<T>.


На счет ошибки компиляции вообще хорошо. Вот написал кто-то Java-код на Java-1.4, отдал вам jar-файл. Вы захотели использовать его из Java-1.5 и...? Вам потребуются чужие исходники, чтобы пересобрать их в новый jar, но уже под Java-1.5?

Ну а какая разница? Старый код ожидает List (который работает с Object-ом), а в новом уже List<T>.

В C# сделали по-умному: дженерик-версия реализует и IList, и IList<T>.


Вы захотели использовать его из Java-1.5 и...?

И в худшем случае просто придётся написать враппер.

В C# сделали по-умному: дженерик-версия реализует и IList, и IList<T>.

В разговоре про проблемы Java, которые возникли из-за стремления обеспечить максимальную совместимость, ссылаться на решения из .NET-а — это очень умно.


И в худшем случае просто придётся написать враппер.

Так ведь, насколько я помню, как раз из-за type erasure ничего писать и не приходилось.

В разговоре про проблемы Java, которые возникли из-за стремления обеспечить максимальную совместимость, ссылаться на решения из .NET-а — это очень умно.

Вообще-то решение .NET как раз и обеспечивает максимальную совместимость.


В разговоре про проблемы Java, которые возникли из-за стремления обеспечить максимальную совместимость, ссылаться на решения из .NET-а — это очень умно.

Ага. Вот только сейчас Java почему-то навёрстывает упущенное и в ней появляется функционал, который уже давно есть в C#.

> Так ведь, насколько я помню, как раз из-за type erasure ничего писать и не приходилось.

Сложно понять, почему этот комментарий собирает минусы. Но для тех, кто не застал переход с Java 1.4 на Java 1.5 и не в курсе того, что Java 1.5 с генериками позволяла осуществлять интероп с со старым Java-кодом (причем без перекомпиляции этого старого кода), может быть интересна вот эта ссылка: docs.oracle.com/javase/tutorial/extra/generics/legacy.html

ЕМНИП, у Sun было несколько причин, по которым при реализации генериков было принято решение использовать type erasure. Одной из которых как раз была необходимость поддержать уже имеющееся легаси, причем без необходимости перекомпиляции старого кода новыми JDK.

Код по вашей ссылке мог бы выглядеть вот так:


public class Main {
    public static void main(String[] args) {
        Collection<Part> c = new ArrayList<Part>();
        c.add(new Guillotine()) ;
        c.add(new Blade());
        Inventory.addAssembly("thingee", c);
        Collection<Part> k = (Collection<Part>)Inventory.getAssembly("thingee").getParts();
    }
}

Но решили по-другому. В итоге выиграли всего-то отсутствие нескольких тайпкастов или копирований на стыке старого и нового кода — а проиграли целые автоматические контейнеры для примитивных типов...

Да я уже и не знаю, как с отдельным персонажами разговаривать. Такая оторванность от жизненных реалий, что ППЦ.

По поводу вашего комментария остается только сказать, что Sun потеряла очень многое из-за того, что не привлекла вас к реализации генериков в Java. Уж вы-то… Как же ж иначе.
В середине 90х такие эксперименты велись, и C++ в них участвовал. Можно взять IBM VisualAge for C++ в режиме DirectToSOM, обрести совместимость на уровне сборок, так как механизмы поиска полей заменяются сомовскими, и в этом SOM есть возможности развития многочисленных надстроек. Эксперименты с совместимостью были у Sun (Object Binary Interface), SGI (Delta/C++) и, конечно, Apple и IBM (SOM).

Как я понимаю ход истории, сначала SOM вытеснил их всех, а потом Java вытеснила SOM. Через какое-то время пришло понимание, что Java много, где не очень, и C++ обрёл второе дыхание, вылившееся в C++11. Но в этом C++11 уже не было ни следа перспективных разработок 90х, и комментарий

В отличие от C++, у


относится к этому более позднему периоду, когда C++ занялись всё забывшие рыбки.

А ведь если C++ позволяет дать legacy коду фасад в языконейтральную объектную модель типа SOM, то там не только Nemerle с Kotlin могли бы существовать, но и условный починенный C++, отбросивший обратную совместимость с C, но сохранивший совместимость с моделью типа SOM.

Когда шкаф тяжёлый двигают, им шагают с ноги на ногу. Можно было бы шагнуть, перенеся опору с совместимости на уровне исходников на двоичную совместимость сборок.
Delphi — это как раз попытка развития в духе «C/C++» (относительно успешная до момента, когда кто-то решил, что он умнее всех и сломал совместимость), но вот C# сюда вообще никаким боком не относится, это потомок совсем другого проекта (грубо говоря «Java с блекджеком и шлюхами», родившаяся из судебного решения). Ada — ещё чуть-чуть как-то где-то, но никак не C#, извините.

То-то я с делфи когда переходил, все было одно к одному. Синтаксис — да, сишный, а вот все остальное чувствуется. Рука чувствуется, знаете ли. Одна из причин, почему я именно начал учить шарп, а не джаву — очень похоже на дельфи было всё.

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

Окей, значит все же переломный момент где-то будет. Тогда вопрос: как определить, что этот момент настал? Почему вы так уверены, что 2019 это неподходящий год для того, чтобы выкинуть плюсы, а 3019 — подходящий?

Да, в Delphi (да и C++ Builder), в отличие от C++, уже появилось разделение на классы и интерфейсы.


А ещё WinForms и визуальный редактор были подозрительно похожи на VCL. Собственно, благодаря именно этому я и перешёл с Delphi на C#, потому что все остальные средства создания GUI были ужасны.

Потому что «выкинуть» что-то можно не тогда, когда появляется альтернатива, а когда то, что вы используете перестаёт использоваться!

В случае с C++ — это и близко тне так. И вообще я не удивлюсь если на какой-нибудь Rust люди перейдут не с C++, а, скажем, с C#.

Тот же Go, скорее, привлёк любителей Java и Python, а не C++
Потому что «выкинуть» что-то можно не тогда, когда появляется альтернатива, а когда то, что вы используете перестаёт использоваться!

Ну так как оно перестанет использоваться, если альтернативами никто не пользуется. «Ведь столько на плюсах написано».
если альтернативами никто не пользуется

Что, даже вы не пользуетесь?

Ну вообще-то Климент Шиперский, паскалист Виртовской школы и разработчик Black Box Component Builder сейчас тоже работает где-то в Microsoft. Другой вопрос в том, что C# сделали максимально похожим на C, убрали всё сходство с Algol60/Паскаль заодно и от виртовского минимализма избавились. Так что Шарп, он, конечно, наследник — но какой то незаконорожденный.

А вообще Lua с некоторой оглядкой на Модулу делался, и это и в плане синтаксиса, и в плане минимализма как раз заметно. И в своей нише (встраиваемый в приложения скриптовый язык) Lua конечно испытывает серьёзную конкуренцию со стороны JS (унаследовавшего синтаксис C/C++) но помирать уж точно не собирается.
Дык эта. Никто ж не спорит, что идеи Алгола умерли — часть их много куда перекочевала.
Почему в 80е все писали на Паскале (кто-то на СМ'ках, кто-то на PC, да даже и Mac — тоже изначально был на диалекте Паскаля написан), а уже в 90е — перестали? Во-многом — именно потому, что Модула-2/Оберон и так далее не позволяли использовать сущесвующий код, а C++ — позволял.


Устроят тот же бардак


Справедливости ради, Паскаль там был без присмотра Вирта. Вирт-то давно на Модулу перешёл, соответственно, каждый следующий Mac Pascal, когда уже есть Модула — это не его бардак.

И вот, что там, именно в Паскалях, без Модулы, творилось со слов того, кто пытался писать под каждый OpenVMS: p2ada.sourceforge.net/pascada.htm
Вирт-то давно на Модулу перешёл, соответственно, каждый следующий Mac Pascal, когда уже есть Модула — это не его бардак.
Собственно в этом и беда. Вирт продолжал выпускать новые языки, «наследники»… несовместимые с родителями. Уже Модула-2 не завоевала особой популярности (кое-где использовалась, но нельзя сказать, что активно), а последователи — стали и вовсе чисто экзотикой для развлечения студентов.

Собственно та же судьба чуть не постигла FORTRAN-77: он тоже был изменён несовместимым образом и люди не хотели отказываться от существующих программ. Но, во-первых, многие компиляторы позволяли использовать устаревшие фичи (как многие компиляторы C понимают «старый» до-ANSI C), а во-вторых — больше явных несовместимостей, заставлявших всё переписывать, уже не было.

Вирт же, устроив бардак с выпуском Модула-2 не остановился на достигнутом, а сделал контрольный выстрел в голову — на чём, собственно, его влияние на мейнстрим и окончилось… уже навсегда…

Ну а что разработчики Pascal устроили бардак… А кто мог его предотвратить? Вирт самоустранился, стандарт изначально ничего не значил (стандартный Pascal не поддерживается большинством самых популярных реализаций), а вместо попыток всё это привести в какой-то порядок его автор — занялся своими мёртворождёнными проектами…
С Algol-W Паскаль тоже на уровне исходного кода не был совместим, а Algol-W, я так понимаю, не совместим с Algol до него. Паскаль не был какой-то особенной точкой, если посмотреть на то, что в течение жизни делал Вирт.

И в Модуле поменяли if-then-begin-end-else-begin-end на if-then-else-end-if, что на мой взгляд принесло облегчение. Досадно, что современные FPC, Delphi, Oxygene все они законсервировали эту тупость, о которой Вирт и его сотоварищи сожалели.

Вирт самоустранился


Самоустранилось государство, за исключением языка Ада. Но потом и от Ады государства самоустранились. США от языка программирования, а СССР от существования, и вместе со своим существованием и от Ады.

А Вирт — это ETHZ, то есть, государство. Если не через государство, то через бизнес, а в бизнесе надо быть самым живучим тараканом в ядерной зиме. Какой бы таракан живучий не был, всё равно это таракан, всем своим видом доставляющий неудовольствие. Вирт не захотел был тараканом, видимо, в этом его проблема. А даже если захотеть, вокруг тебя будут другие тараканы. Вот C++, например, в какой конкурентной обстановке появлялся

C-talk — это запатентованный язык для MS-DOS и Apple Macintosh, созданный отделом программных продуктов фирмы CNS Inc, специализирующейся на продуктах диагностики для медицины. Синтаксис языка основан на Си с дополнительными конструкциями, поддерживающими классы и методы, наследование и посылку сообщений


Complete Computer Corporation, New York работает над созданием объектно-ориентированной среды разработки программ, называемой Complete C. Она состоит из прекомпилятора, генерирующего стандартный Ansi С из объектно-ориентированного Си,


Возможности повлиять Вирта на развитие ветки Паскаля можно сравнить с возможностями повлиять Complete C на развитие ветки C. Без помощи государства слишком много завязано на везение и грязные трюки. Когда делал Паскаль, повезло и выстрелило, а с Модулой — нет.

сделал контрольный выстрел в голову — на чём, собственно, его влияние на мейнстрим и окончилось… уже навсегда…


О нет, это влияние было очень мощным и до сих пор сохраняется, только не в виде конкретного языка программирования, а в виде КОП, которое, я долго не мог понять, о чём вообще, а оказалось, что про то, чтобы между компонентами (сборками) был запрет на наследование. Отсюда COM, отсюда XPCOM в моём Comodo IceDragon на базе Firefox, Windows Runtime в моём Windows 10, и VirtualBox, в котором крутятся мои не-Windows виртуалки на хостингах. До сих пор кругом он. В то время, как поддерживающая наследование Java вытесняла поддерживающую наследование SOM, кто-то очень угорел по запрету наследования.
С Algol-W Паскаль тоже на уровне исходного кода не был совместим, а Algol-W, я так понимаю, не совместим с Algol до него. Паскаль не был какой-то особенной точкой, если посмотреть на то, что в течение жизни делал Вирт.
Pascal был особой точкой — но дело не в Вирте. Революцию, как мы теперь знаем, совершила IBM, выпустив IBM/360в 1964м… но настоящее её осознание — пришло чуть позже, когда вышла IBM/370 и начала набирать популярность PDP-11. С этого момента софт перестал писаться под конкретный, имевшийся у вас, компьютер — и появилась потребность в переносимости.

Её не сразу распознали, первый звоночек прозвенел, когда у FORTRAN 77 появились проблемы из-за отказа от Холлеритовых констант. Люди не хотели переписывать свои программы!

Второй звоночек прозвенел, когда появилась IBM PC. К ней, как известно, предлагались на выбор три операционки: PC DOS, CP/M-86 и UCSD p-System. Последняя имела такой «оглушительный» успех, что сейчас отдельные уцелевшие экземпляры продаются по цене в тысячи долларов на eBay. Потому что тормозит же, сволочь!

Третий не звоночек, а, скорее, вспышка — это выход Turbo. Когда Фил умудряется за несколько продать 200 тысяч копий — притом, что количество «действующих» программистов до того, как он начал продавать свой компилятор на $49.95 оценивалось в 20 тысяч (вообще всех) и он надеялся продать 400-500 копий для того, чтобы окупить рекламу, которую он дал в кредит!

И вот после всего этого, вместо того, чтобы взять Pascal (более популярный) или Модула-2 (несколько менее популярная, но чуть более «продвинутая») и начать его развивать… Вирт создаёт Оберон и помогает создать Модулу-3. А первый — так ещё и используется для написания своей собственной, ни с чем не совместимой, OS. Ну это же уму невыразимо!

Вирт не захотел был тараканом, видимо, в этом его проблема
Вирт не заметил (или, скорее, не захотел заметить) что время «уникальных архитектур» прошло. Последней, вот совсем последней операцинкой, которую удалось запустить «с нуля» — была MacOS. Хотя и то: она как раз на Pascal была написана (хотя и довольно сильно модифицированном Pascal). Всё остальное, что завоевало популярность — делалось на базе существующих разработок.

Иногда удавались попытки (довольно-таки дерзкие) привить на базу одной операционки другую, иногда сильно другую (как, скажем, Mac OS X на замену Mac OS Classic), но никто уже никогда не пытался одновременно сменить ОС и операционку. Даже какие-нибудь Psion или Palm — всё равно опирались на вездесущий GCC (который, в свою очередь, стал вездесущим потому, что работал везде и всюду).

Когда делал Паскаль, повезло и выстрелило, а с Модулой — нет.
Нет — не «повезло и выстрелило». Pascal- это 1970й год, когда совместимость ещё не рулила миром, Modula-2 — это 1978й год, когда ситуация уже изменилась, но мир этого ещё не понял. Но какие-то шансы были: Wikipedia перечисляет больше 20 диалектов. После дальшейшего «расщепления древа» (Modula-3, Oberon и так далее) — всё, они иссякли…

В то время, как поддерживающая наследование Java вытесняла поддерживающую наследование SOM, кто-то очень угорел по запрету наследования.
Ну это уже совсем другая история. Эти идеи далеко не только Вирт высказывал…
UFO just landed and posted this here

Ну дык, об этом я и говорю. Любой тип Т — инициализированный, инвариант сохраняется, MaybeUninit<T> — нет, и такты не тратятся.

UFO just landed and posted this here
ADT tagless, стирается в рантайме. Так что нет, не тратится.

Для тех же причин, зачем есть NonZero.
UFO just landed and posted this here
Ниоткуда. Вы должны заранее знать что там лежит. Не случайно все функции читающие значение из MaybeUninit объявлены как unsafe.

Если вы читаете значение, то предполагается, что оно там есть. Чтение неиницализированного значения — UB.


As you are probably discovering, mem::uninitialized makes it trivial to shoot yourself in the foot, I'd go as far as saying that it is almost impossible to use correctly. That's why we are trying to deprecate it in favor of MaybeUninit, which is a bit more verbose to use, but has the benefit that, because it is an union, you can initialize values "by parts" without actually materializing the value itself in an invalid state. The value only has to be fully valid by the time one calls into_inner().
UFO just landed and posted this here
Так в безопасном коде он и не даёт же.
UFO just landed and posted this here
Нет, это безопасная операция. Но, оставаясь в рамках безопасного кода, вы не сможете ничего с этим MaybeUninit сделать, кроме как перезаписать его другим MaybeUninit.

Как выше сказали, вы не сможете достать значение без использования unsafe. А бездумное использование unsafe может приводить к UB, да.


На момент материализации значения оно должно быть валидным, и язык явно возлагает на разработчика ответственность, чтобы он не материализовывал объект в невалидном стейте.

UFO just landed and posted this here

Она валидирует на своем уровне.


Как вам система типов проверит что-то в FFI сценарии? Ведь MaybeUninit в основном используется именно для него. Выделить буфер, куда сишечка что-нибудь напишет, а потом прочитать ответ, вот это всё. Расскажете про язык, где этот вопрос решен на этапе компиляции без проверок?

UFO just landed and posted this here
Она валидирует на своем уровне.
Ну это так себе валидация, если после неё какие-то рантайм-UB могут быть.
А никакой другой и нету. У какого-нибудь Хаскеля могут быть очень даже заметные UB, если в OS, в которой его запустят какой-нибудь mmap реализован с ошибками.

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

Возможность создания «безопасного языка» в котором всё, начиная с транзисторов верифицировано и доказано остаётся открытой — и его уж точно невозможно будет запускать на существующем железе.

Как вам система типов проверит что-то в FFI сценарии?
Никак, FFI — ансейф почти по определению. Но я говорю не об FFI, а о коде на самом языке.
А почему, извините? Почему, собственно, тот факт, что небезопасный код вы должны писать на другом языке и присоединять к безопасному коду через страшные прослойки — считается достоинством, а не недостатком?
UFO just landed and posted this here
Никак, FFI — ансейф почти по определению. Но я говорю не об FFI, а о коде на самом языке.

А в самом языке такие проблема пока есть, но будут решены. А MaybeUninit — это средство для FFI, ну и по совместительству костыль на то время, пока хорошие решения не стабилизированы.
UFO just landed and posted this here
Есть неплохое выступление Мейерса, в котором он разбирает странности C++ www.youtube.com/watch?v=KAWA1DuvCnQ
Так вот он акцентирует внимание на том, что всё это не плохо, это не идиоты в комитете, у всего есть нормальное объяснение, почему именно так, а не иначе. Да, C++ сложен, но и задача, которую он перед собой ставит не тривиальна. Сложная задача не решается просто. Совсем отдельный вопрос, актуальна ли задача и нельзя ли было взять нишу поуже.

Ну да, все верно, С и С++ разные языки, одинаковыми они кажутся только тем кто не знает ни того ни другого.
Хочу еще напомнить что


int x, y{};

значение x останется неопределенным, а y будет инициализован 0 и это сделано специально (неявный дефолтный конструктор не вызывается для встроенных типов) для тех редких случаев когда надо экономить каждый такт процессора. Это нужно исключительно редко, но те кому надо узнают и запоминают мгновенно, один раз услышав. Для остальных есть мнемоническое правило — инициализовать переменные при обьявлении, и что в этом плохого?
Такой вот язык С++, и мне он таким нравится.

Если это не глобальная или статическая переменная, то «x» тоже будет 0 абсолютно на всех существующих компиляторах, совместимых с любым из стандартов. С нулевым количеством расходования тактов процессора.
Строго наоборот — нулём окажется глобальная или статическая переменная. А вот в локальной будет «лежать» мусор.
Там так и напсиано. Это же ответ на предыдущий комментарий:
— "… значение x останется неопределенным, а y будет инициализован 0..."
-«Если это НЕ глобальная или статическая переменная, то...»
В том плане, что предыдущий коментарий верен только если это НЕ глобальная или статическая переменная.
Врыазился, конечно, наикривейше — факт.
Интересно, что C++ уже 30 лет игнорирует то, что такие переменные имеют нулевые значения, не добавляя это в стандарт (хотя это не поломает никакой совместимости).
Интересно, что C++ уже 30 лет игнорирует то, что такие переменные имеют нулевые значения, не добавляя это в стандарт (хотя это не поломает никакой совместимости).

Ну кто вам рассказал такую глупость? Написали простейший кусок кода типа hello world и "проверили"?


Конечно, если стек изначально забит нулями, то первый раз в переменной будет ноль, но после пары вызовов функций там будет уже мусор. Ну или динамическая память — изначально она тоже забита нулями, пока менеджер динамической памяти не начнёт её переиспользовать.

Статические переменные живут не в стеке, а выделяемая для них память сразу заполняется нулями во всех (или почти во всех) операционных системах при загрузке программы в память.
Память заполняется нулями во всех средах, соответствующих стандарту — о чём там написано. Составить пример, когда неинициализированная переменна на стеке будет содержать мусор — тоже несложно. Так что о чём пишет Sap_ru — остаётся только догадываться.
Интересно, что C++ уже 30 лет игнорирует то, что такие переменные имеют нулевые значения, не добавляя это в стандарт (хотя это не поломает никакой совместимости).
Это такой тонкий троллинг? Про это написано во всех стандартах начиная с C90! Вот как написали в стандарте If an object that ha> automatic storage duration is not initialized explicitly. its value is indetelminate. If an object that ha\ static storage duration is not initialized explicitly. it is initialized implicitly as if every member that has arithmetic type were assigned 0 and every member that has pointer type were assigned a null pointer constant. в самом первом стандарте — так это и кочует из стандарта в стандарт.

Никогда не задумывался над этим огромным полем из граблей, всегда ходил по тропинкам, где их нет :-). Очень интересно было.


Как в последнем примере: я не напишу так, даже если буду точно знать, где какой конструктор сработает. "Пиши код так, будто поддерживать его будет склонный к насилию психопат, знающий, где ты живёшь".

В современных плюсах вроде модно так писать:


auto a = A{};

Половина проблем уже ушла, ну а если свой конструктор пишем то понятно что там надо все самим инициализировать правильно.


А вот на си писать студентам не надо. Без RAII код замусорен всякими new, delete, close(). К тому же размер массива и данные хранятся в двух разных переменных. С таким излишними сложностями об алгоритмах некогда поговорить.

Вы таки, видимо, не умеете си? Откуда в си new, delete? Эти операторы есть только в плюсах...


Про размер массива тоже не очевидное утверждение. Кто мешает использовать структуру, если очень хочется? А во многих случаях со статическими массивами справляется sizeof.

Ну а в чем проблема то? =))
#define new malloc
#define delete free

Перепутал с malloc и free. Но смысл тот же, без std::vector не удобно.

Sizeof это вообще те ещё грабли, смотрите на статьи от единорога. Вы видимо хорошо знаете си если у вас это не вызывает сложностей. Зачем если есть std::array?

Конечно некропост, но. Грабли-то конечно грабли. Если так посмотреть, то в принципе что С что С++ — это "минное поле" из граблей (потому что undefined behaviour проскакивает даже у очень опытных разработчиков — это кстати о единорогах, без статического анализа никуда). Но если помнить, что sizeof работает для массивов только в области видимости объявления этого самого массива — то жить можно. При чём тут std::array, я не понял. Мы же про С говорим, откуда там std::array? В плюсах никто использовать "сырые" массивы в стиле C не предлагал.

Может я не так понял статью, но автор предлагал вместо С++ учить студентов Си, потому что видите ли в плюсах сложные конструкторы а про алгоритмы поговорить не когда. Так вот как раз всё на оборот. В плюсах достаточно понять как создавать vector и уже можно легко обсудить все алгоритмы сортировки. На сях начинающий студент начнёт писать гавнокод с malloc без free и прочие приколы, из-за этого не возможно сконцентрироваться на задаче.

Я вас умоляю. Если вы хотите понять, как работают алгоритмы и что такое сложность сортировки — вам даже C не нужен. Достаточно Бейсика 70х с двухбуквенными переменными-масчивами и всё.

Писать же говнокод можно на любом языке — а вот красивый и понятный не на любом.

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

Засада-то как раз в том, что "понять как создавать vector" в плюсах — это нетривиальная задача. Статья об этом. Потому что правила инициализации — адъ.
Если мимоходом сказать что "вот std::vector a(10);" это массив не сильно об этом думайте, то это как бы не сильно-то честно — с таким подходом любой язык сойдёт, включая си ("вот int a[10];" это массив...). А если "честно" учить плюсам, то вдруг неожиданно оказывается, что мы начинаем обсуждать правила вывода шаблонов, потом добираемся до разных типов конструкторов, обсуждаем правила выбора перегрузок конструкторов, обсуждаем правила инициализации, а тут глядишь и семестр закончился, а сортировку так и не обсудили. Поэтому я понимаю автора. Научить плюсам "мимоходом" невозможно. Я больше 10 лет с ними работаю, и до сих пор время от времени натыкаюсь на неожиданно неочевидные моменты (в основном конечно потому что новые стандарты завозят "неожиданно неочевидные моменты" пачками).

В си всё немного проще. Меньше "лишних" сущностей. То есть я ни секунды не спорю, что писать на плюсах часто приятнее, чем на сях. Но использовать плюсы как "первый детский язык" — тем более не как основной предмет обсуждения, а как "ну какой-то язык алгоритмы обсудить" — это крайне сомнительное мероприятие.


При этом, я, конечно, отмечу, что использовать Си как "первый детский язык" тоже достаточно сомнительный вариант. Потому что UB. И на него обязательно кто-то наткнётся, а объяснить "что такое UB и как с ним жить" быстро, просто и понятно — невозможно. Не забываем конечно же при этом, что тот же самый UB есть и в плюсах, поэтому к плюсам эти рассуждения применимы в той же мере. Но если выбирать "из двух зол", то Си кажется более простым вариантом. Не в том смысле, что на нём проще писать. А в том смысле, что его реалистично более-менее объяснить за полсеместра и останется время "об алгоритмах поговорить".

Потому что UB

А это разве плохо? Лучше набить шишек на C, где потом ловить их в C++ и других менее прямолинейных языках.


Минус C только один: если переходить с него на C++, то это будет выглядеть как C с классами.

и других менее прямолинейных языках.

Назовите "другие менее прямолинейные языки" с таким зоопарком UB как в C или C++. Я вот затрудняюсь.


Лучше набить шишек на C

Это всё хорошо если задача "выучить C". А если задача "научиться алгоритмам" или там ещё что, то "набивание шишек" происходит в ущерб основной задаче.


А вообще UB это в целом скорее плохо, хотя бы потому что, как я выше сказал, проскакивает даже у очень опытных программистов на C и/или C++, чего уж говорить о юниорах. Статический анализ помогает, но это не "серебряная пуля". В результате какие-то невероятные человеко-часы тратятся на отладку, или ещё хуже, оно потом не пойми с чего валится "в бою". На мой вкус, совсем не хорошо.

Много программистов на плюсах в SFINAE и в мета-программирование на шаблонах не влазят. А что такое template понять не сложно. Другое дело что есть лучше языки наверное для новичков. int a[10] массив хорошо пока размер известен в compile-time. Да с конфликтом конструкторов из-за std::initilizer_list это жёсткий косяк дизайна. По поводу остальных проблем, лучше всегда прописывать значения явно и не надеятся что примитивные типы по умолчанию будут ноль.

Много программистов на плюсах в SFINAE и в мета-программирование на шаблонах не влазят.

Пусть себе не влазят. Чтобы эффективно использовать template-ы даже без SFINAE и хардкорного мета-программирования, надо знать правила вывода шаблонных аргументов. Иначе иной раз возможны сильно неприятные сюрпризы. Правила и так-то не всегда очевидны, а если учитывать ещё и C++11 с "универсальными ссылками" (которые на самом деле фикция) и C++17 с выводом шаблонных аргументов классов, неожиданно оказывается, что за 10 минут даже просто рассказать внятно про это всё не получается.


int a[10] массив хорошо пока размер известен в compile-time

Начиная с C99, то есть уже без малого 20 лет, C умеет переменную длину массивов на стеке, определяемую в run-time. Вы, наверное, с C++ опять попутали? C++ действительно по стандарту не умеет до сих пор (хотя подвижки есть, плюс компиляторы поддерживают в виде расширения)


По поводу остальных проблем

Там "остальных проблем" ещё вагон и маленькая тележка. Ну не получается учить плюсам "мимоходом". Или в результате имеем юниоров, которые на бумаге "умеют плюсы", а на практике в коде какой-то кромешный ад. Из реально попадавшегося, многомерные массивы в виде T*** или не лучше vector<vector<vector<T> > >, массивы из пяти элементов в куче вместо стека, использование в паре new[] и delete, вообще использование new и delete где без них можно обойтись, использование std::vector для трёхмерных векторов… ну и естественно регулярно "у меня не компилится", или хуже того "у меня компилится, но не работает"… Извините, наболело.

C++ действительно по стандарту не умеет до сих пор (хотя подвижки есть, плюс компиляторы поддерживают в виде расширения)
Вот только это расширение делает универсальные ссылки… эта… неуниверсальными…

Не понял Вас. "Универсальные ссылки" это T&&, где T — шаблонный тип. Любой C-style массив на стеке, что динамический что нет, всё равно распадается в указатель до того как будет выводиться T, соответственно непонятно как это самое расширение, включающее в плюсах динамические массивы на стеке в стиле C99, что-то делает с "универсальными ссылками". Мы точно об одном и том же говорим?

Любой C-style массив на стеке, что динамический что нет, всё равно распадается в указатель до того как будет выводиться T, соответственно непонятно как это самое расширение, включающее в плюсах динамические массивы на стеке в стиле C99, что-то делает с "универсальными ссылками"

Не-а:
https://gcc.gnu.org/onlinedocs/gcc/Variable-Length.html


Прикол в том, что можно передавать ещё и двумерные массивы в качестве аргументов. И если одномерный массив ещё можно преобразовать к указателю без потери семантики, то двумерный — нет.


Суть в том, что массивы на стеке невозможно переместить.

И если одномерный массив ещё можно преобразовать к указателю без потери семантики, то двумерный — нет.
Семантика, всё-таки, будет потеряна. При передаче массива в функцию по ссылке его размер тоже передаётся. И да, двумерные массивы — тут всё ещё хуже. Но чтобы «универсальные» ссылки перестали быть универсальными — хватает одномерных.

Потому в C++20 их и нету (хот в clang и gcc они есть, но, как уже сказано, они плохо взаимодествуют с другими вещами в C++).
При передаче массива в функцию по ссылке его размер тоже передаётся.

Размер же передаётся явно одним из параметров.

Я это и имел в виду. Размер динамического массива в С99 передаётся отдельным параметром, а не является частью указателя на массив. Без этого дополнительного параметра передать динамический массив, не преобразовывая его к указателю, попросту нельзя.


Чисто теоретически, ничто не мешает сделать расширение C++, в котором можно было бы передавать такие массивы без дополнительных параметров, представляя их в виде какого-нибудь std::stack_array. Но, во-первых, это решение не будет совместимо с соответствующим расширением C99. А во-вторых, будут проблемы с move-семантикой (на самом деле, она не будет отличаться от std::array, но смысла в ней будет мало).

Любой C-style массив на стеке, что динамический что нет, всё равно распадается в указатель до того как будет выводиться T
Вы издеваетесь, что ли?

А как такая программ компилируется, по вашему:
template<typename T>
constexpr int foo(T&& param) {
   return sizeof(param);
}

void bar() {
    char baz[10];
    static_assert(foo(baz) == 10);
}

соответственно непонятно как это самое расширение, включающее в плюсах динамические массивы на стеке в стиле C99, что-то делает с «универсальными ссылками»
Дык эта, вот так:
template<typename T>
int foo(T&& param) {
   return 42;
}

void bar(int x) {
    int arr[x];
    foo(arr); // <-- error
}


Мы точно об одном и том же говорим?
Думаю что да — просто вы забываете про то, что массивы и указатели в C++ — это всёж-таки разные типы. Хотя и преобразующиеся друг в друга…
Вы издеваетесь, что ли?

Нет, не издеваюсь, туплю. Думал о передаче by-value, то бишь не T (&) [N], a T [N], который всегда вроде бы превращается в T[] и распадается до T*.


Дык эта, вот так:
variable-sized array type 'int (&)[x]' is not a valid template argument

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

Нет, не издеваюсь, туплю. Думал о передаче by-value, то бишь не T (&) [N], a T [N], который всегда вроде бы превращается в T[] и распадается до T*.
А by-value массив как раз всё равно передать нельзя — но и никаких ссылок при передаче как T [N] нету. Тем более универсальных.

P.S. Вообще посмотрев на это, а также задумавшись над многомерными VLA (которые тут выше уже упомянули) — начинаешь осознавать насколько глубока кроличья нора и как много придётся всего перелопатить, чтобы VLA нормально в C++ интегрировать. Появятся «нормальные» дженерики в стиле Ada/C# (где во время компиляции размеры неизвестны, а настраиваются они во время компиляции), производные от них типы и куча всего ещё. Оно, как бы, всё красиво и круто — только вот потребует нескольких человеко-лет для того только, чтобы спеку написать… а потом это ещё реализовывать… Нафиг-нафиг. А вот как расширение компилятора «валидная C программа в режиме C++ у вас соберётся — а что и как там дальше, нас не касается»… так это гораздо проще сделать…
никаких ссылок при передаче как T [N] нету

Само собой. Поэтому и говорю что "туплю".


[сложно] VLA нормально в C++ интегрировать

VLA кстати сделали необязательным в C11. Не исключено, что, в том числе, поэтому.


А вообще делать VLA в плюсах по аналогии с си — немного странно. Собственно и статические массивы в стиле си настойчиво предлагают заменить на std::array. Да и уж без n-мерных массивов в стиле си обойдёмся как-нибудь, даром что перегрузкой операторов можно добиться аналогичного эффекта. А вот что в плюсах на текущий момент (C++20) нет нормального способа выделить маленький динамический массив на стеке — это иногда ощутимо неприятно.

А вообще делать VLA в плюсах по аналогии с си — немного странно.
Ещё страннее делать их несовместимыми с C.

Да и уж без n-мерных массивов в стиле си обойдёмся как-нибудь, даром что перегрузкой операторов можно добиться аналогичного эффекта.


Не получится. Потому что размеры-то в C99 указываются в рантайме:
int n;
void foo(int x[n][n]);

void bar() {
    int arr1[2][2];
    int arr2[3][3];
    n = 2;
    foo(arr1);
    n = 3;
    foo(arr2);
}


А вот что в плюсах на текущий момент (C++20) нет нормального способа выделить маленький динамический массив на стеке — это иногда ощутимо неприятно.
Да, но добавлять вот одну эту фичу без всего остального, что умеет C99 — несколько странно…
Не получится

Не вижу проблемы сделать обертку вокруг std::vector которая на operator[] возвращает нужный слайс на измерение меньшей размерности. Размеры тоже в рантайме. И даже, по-моему, расположение в памяти такое же.


страннее делать их несовместимыми с C

С++ уже давно очень условно совместим с C. ИМХО пора бы уже смириться с тем, что это разные языки и перестать тратить сотни человекочасов на обеспечение сомнительной совместимости. Как минимум идиоматика на плюсах очень сильно отличается от идиоматики на си. Если тянуть VLA один в один из си, то получится чёрт знает что в этом смысле — хотя бы на уровне std::array для статических массивов и C-style для VLA. Вспомним ещё на секунду что C-style массивы не то чтобы совсем уж STL-совместимые и становится совсем грустно.


добавлять вот одну эту фичу без всего остального

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

Какой-то говнокод, как по мне, зачем там вообще auto?
A a{};
auto годится только для всяких мутных типов которые совсем лень писать вроде итераторов и прочих map::value_type с которыми легко ошибиться при ручном написании.
auto годится только для всяких мутных типов
А зачем вообще нужно дублировать имя типа там где он указывается явно? Не вижу вообще смысла не использовать auto кроме как для простых типов данных, случаев, когда тип переменной расходится с типом присваиваемого значения, и возможно еще каких исключений.
Я раньше был большим поклонником использования auto, но сейчас прихожу к мысли что все хорошо в меру.
Если в коде тип действительно вообще никак не влияет на понимание кода, если это какой-то там vector<string>, то auto может быть уместно. Если я только что сделал make_unique, то тоже тип и так очевиден. Во многих местах видеть тип реально сокращает время чтения кода. Многие пишут мол IDE, и так все видит, выводит типы, но я читаю код очень быстро, проматывая по странице в 2-3 секунды, и мне такие визуальные зацепки очень помогают.
Если нужно убрать когнитивную сложность из кода, скрыв «мусорный» тип, то auto вполне в тему)

Да это те же люди, которые были против var в C# и других языках. Инерция мышления, однако.


Хаскеллисты и компания всю жизнь пишут let на всё подряд и никогда не страдают от того, что где-то чего-то непонятно.

UFO just landed and posted this here

В хачкеле емнип только топ декларации пишут, а отдельные личности и их опускают)


А я скорее про сторонников Person person = new Person()

В Хаскеле просто охренительный вывод типов + система типов гораздо более приспособлена для этого, чем в C++ (нет неявного приведения типов).

Как по мне, это абсолютно нормально. Например, могут быть случаи, когда нужно вызвать именно конструктор по умолчанию, а не передать список инициализации. Тогда вы можете написать так:


auto a = A();

Но написать так:


A a();

вы не сможете, придётся писать:


A a = A();

А дублировать название типа с обеих сторон (а оно может быть длинным) — это не очень хорошая практика.

А раскрыть глаза и посмотреть на то, что вам предлагают нельзя? Написано
A a();
действительно нельзя, а вот
A a{};
написать можно всегда.

Может стоит уже начать забывать про C++98? Прошлый век, всё-таки…
А раскрыть глаза и посмотреть на то, что вам предлагают

А предлагают фигню. Я хочу меньше думать над нюансами языка и больше — над задачей. Я реально не хочу думать над тем, где в случае использования фигурных скобок будет вызываться обычный конструктор, а где — конструктор, принимающий на вход std:initializer_list. Поэтому писать {} в качестве исключения для вызова конструктора по умолчанию меня несколько коробит.

А предлагают фигню.

Предлагают вещи, которые облегчают жизнь. Вряд ли кто-то в C++ до 11-го стандарта не наступал на грабли A a(). Сейчас про них вообще можно забыть, если использовать {}.


Кроме того, {} позволяют вам инициализировать POD-структуры:


struct S { int a_; int b_; int c_; };

int main() {
    S s{ 0, 2, 6 };
}

Кроме того, {} защищают вас от неявных усечений при инициализации:


int main() {
    char a = 13045; // Предупреждение.
    char a2{13045}; // Ошибка компиляции.
    char a3(13045); // Предупреждение.
}

(цинк)


Так что инициализация через {} в современном C++ лучше в большинстве случаев, чем использование () из старого C++.


Да, при этом вылезли косяки с контейнерами, для которых есть конструкторы с initializer_list, поэтому std::vector<int>{10, 0} оказался не эквивалентен std::vector<int>(10, 0). Но это очередная ошибка эволюции. Бывает.

Вряд ли кто-то в C++ до 11-го стандарта не наступал на грабли A a()

Я не наступал. Просто имел привычку либо объявлять конструктор по умолчанию, в котором инициализировал поля нулями, тогда можно было спокойно писать A a, либо инициализировал поля руками.


Предлагают вещи, которые облегчают жизнь.

Что реально добавило удобств, так это copy elision.


Кроме того, {} позволяют вам инициализировать POD-структуры:

Оно и раньше позволяло так делать.


Да, при этом вылезли косяки с контейнерами, для которых есть конструкторы с initializer_list ...

Поправили одно — вылезло другое. Не заглядывая в описание класса, невозможно понять, что перед нами: параметры конструктора или список инициализации. Как по мне, так косяк довольно серьёзный, и подобный функционал нужно использовать с максимальной осторожностью. Ведь есть же абсолютно логичное решение: std::vector<int>{{10, 0}}.

Я не наступал.

Может вы просто шаблоны не писали. А то ведь:


template<class T> void demo() {
  T t; // Как здесь получить дефолтную инициализацию?
  ...
}

Что, если T — это int?


Оно и раньше позволяло так делать.

Такое ощущение, что вы в другое время жили. Ибо, вот что говорит gcc-4.7 (древнее у меня нет) с ключиком -std=c++98:


t1.cpp: In function 'int main()':
t1.cpp:4:7: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11 [enabled by default]

А вот что говорит не менее древний VC++ с версией 17.00 (это вроде 2012-я студия):


t1.cpp(4) : error C2601: 's' : local function definitions are illegal
        t1.cpp(3): this line contains a '{' which has not yet been matched
t1.cpp(4) : error C2143: syntax error : missing ';' before '}'

Как по мне, так косяк довольно серьёзный

А кто-то спорит? Да, косяк оказался серьезным. Но не ошибается лишь тот, кто ничего не делает.


Ведь есть же абсолютно логичное решение

Задним умом все сильны.

Что, если T — это int?

Не было такого на практике.

Ну а если бы пришлось, то что?

Тогда я бы написал так:


T a = T();

Ну вот а кто-то писал вот так: T t(). И наступал на грабли. Которые теперь убраны.


ЕМНИП, в свое время были споры, сколько реально создается объектов при записи T a=T().

ЕМНИП, в свое время были споры, сколько реально создается объектов при записи T a=T().
До C++17 компилятор имеет право выбирать. Хотя в большинстве существующих компиляторов — всё-таки один. Хотя если нет конструктора копирования — то это ошибка компиляции.

В C++17 гарантированно один — но это уже тонкости того же порядка, что и использованием гораздо более простой конструкции A a{};
> До C++17 компилятор имеет право выбирать.

По-моему, еще в первом издании «Язык программирования C++» говорилось, что в этом случае будет конструироваться всего один объект. Но это было еще до принятия стандарта, как там это описывалось в C++98 я уже и не помню (а может и не знал).
До C++14 включительно считается, что конструируется два объекта (один с конструктором по умолчанию, потом его копия), но компилятор имеет право убирать конструкторы копирования, если единственным способом заметить его исчезновение — по его побочным эффектам.
А ничего, что эти записи не эквивалентны? И ваша конструкция не работает с объектами, которые нельзя копировать?
UFO just landed and posted this here

Писал, но у меня никогда не было необходимости инициализировать значения изнутри.

Это не я придумал, было в одном из постов Herb Sutter. Ну а что зато похоже на rust:


let a = A::new();

Такая конструкция не лишена логики. Лишнее ключевое слово даже при явном указании типа значительно упрощает парсер и читаемость кода.

У конструкции
auto a = A{}
есть большой плюс в сравнении с
A a{};
— в ней невозможно забыть проинициализировать переменную. Компилятор обязательно выругается, если вы напишете auto a;, но молча пропустит A a;.
Если учить студентов сначала Си, а потом Си++, то в итоге они пишут на Си++ код, состоящий из ручного управления памятью, константных указателей на указатели на short int, голых массивов и глобальных переменных.
Ну так а решение-то какое? Если учить в обратном порядке, получается Сишный код с кучей утечек, дурацких memset которые зануляют только первые 4 байта и т.д.

По-моему в обоих случаях преподаватель должен четко давать понять что мы учим новый язык с нуля.
Решение: сначала начинать с STL, потом уже переходить с базовым Сишным вещам. Пусть уж лучше пихают vector и shared_ptr куда не попадя, чем пишут программы, результат которых зависит от модели процессора (реальный случай в моей практике).
Вчерашние студенты не пишут код, где критична производительность. Надо будет — разберутся. Я считаю, что жыр ради стабильности — нормальный размен.
Все равно что сказать что выучив основы одного единственного языка, все остальные языки и парадигмы становятся недоступными.
ИМХО. все написанное следует воспринимать не как «смотрите как сложно объяснять студентам правила инициализации в C++», а как «вот по каким правилам компилятор будет действовать, если вы, неучи, забудете сделать проинициализировать свои значения». С вполне однозначным выводом, который до народа пытаются донести с начала 1990-х (если не раньше): всегда инициализируйте переменные и члены класса. Всегда. Если только вам не нужно экономить каждый такт.
UFO just landed and posted this here

А мне не нравится инициализация в описании класса при наличии конструктора.
Ходи потом по файлам и разбирайся какое значение должно быть.

Меня еще немножко бесит спецификатор const.
Справа, слева, внутри…
Гораздо хуже constexpr. В случае с переменным — понятно зачем он нужен, а вот в случае с функциями… лишняя сущность, на самом деле, только чтобы разработчиков мучить.
Тогда вам от осознания того, что теперь еще добавился consteval, вообще плохо станет.
Вот как раз consteval накладывает на функцию ограничения и чего-то, всё-таки, значит. А constexpr - эта такая пометка "если надо - ну вычисли это в компайл-тайм, а если не очень или не получается... то и чёрт с ним - будет обычный вызов"...

Ну и нафига это нужно? "Попробовать и, если не получилось, ругнуться" - компилятор мог бы и с любой обычной инлайн-функцией.
а вот в случае с функциями… лишняя сущность

Ну а вот если в compile-time нужно размерность для std::array подсчитать в зависимости от нескольких параметров, то без constexpr-функций варианта всего два:


  • либо вычисления прямо по месту объявления std::array вписывать (дублирование кода);
  • либо использовать #define, в котором своих граблей полно.

И как-то оба эти варианта не фонтан.


И это не углубляясь в тему построения perfect-hash-ей в compile-time, контроля валидности форматных строк (как в свежих fmtlib), построения парсеров для регулярок и пр.

Вы путаете возможность что-то посчитать в компайл-тайм и объявление функции constexpr.

Считать что-то в компайл-тайм — удобно и полезно. Объявлять функции как constexpr — интеллектуальная мастурбация. Ибо в constexpr-функции разрешено, фактически, всё, что угодно. Вы можете создавать объекты и кидать исключения, читать из файла и писать в него, вызывать обычные (не inline и не constexpr функции) то есть делать вообще что угодно. И, если всё это безобразие происходит не там где нужна константа — программа будет валидной!

Ну и кому, нафиг, всё это нужно? Если чтобы понять — будет что-то вычислено во время компиляции или нет всё равно нужно детективом работать?

Если бы constexpr значит то, что в C++ обозначает consteval — вопросов бы не было.
Вы путаете возможность что-то посчитать в компайл-тайм и объявление функции constexpr.

И на каком основании вы делаете такой вывод?


Вот чего я не понимаю, так это вашего возмущения наличием constexpr для функций. Поясню причину моего непонимания.


В C++98 для того, чтобы что-то подсчитать в компайл-тайме, мне нужно было либо использовать #define, либо погружаться в TMP, либо вообще использовать внешнюю кодогенерацию.


В C++11 мне дали constexpr, а потом constexpr улучшили. Теперь я могу делать многое из того, что мне нужно было раньше гораздо более простыми средствами. Что, как по мне, просто очень хорошо.


Цена за это — специальная отметка constexpr на тех функциях, которые могут быть вызваны в compile-time. Как по мне, как цена вполне приемлемая. Вон, в OCaml, рекурсивные функции нужно let rec помечать и ничего: выглядит коряво, зато работает.


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

Вы путаете возможность что-то посчитать в компайл-тайм и объявление функции constexpr.
И на каком основании вы делаете такой вывод?
На основании того, что вы тут рассказваете про C++98, про то, как круто, когда функции можно вызывать в рантайме и всё такое прочее. Но совершенно не хотите понять того, что всё это не требует модификатора constexpr на функции.

Более того — то, что какая-то функция его имеет вам не даёт ровным счётом ничего — вы не знаете можно эту функцию использовать, чтобы «размерность для std::array подсчитать в зависимости от нескольких параметров»!

Вон, в OCaml, рекурсивные функции нужно let rec помечать и ничего: выглядит коряво, зато работает.
В Ocaml работает, а в C++ — нет.

Так что мне решительно непонятно, что вас так в существовании constexpr-функций возмущает.
Меня возмущает, что вот это вот:

#include <iostream>

constexpr void this_is_stupid(bool stupid_arg = true) {
    if (stupid_arg) {
      std::cout << "WTH is this?" << std::endl;
    }
}

int main() {
    this_is_stupid();
}
это, блин, корректная программа.

Вот нафига нужна пометка, которая ни фига не гарантирует? Компилятор мог бы и так проверять inline функции на вычислимость в компайл-тайм и позволять их использовать там, где нам нужна константа.

Вот consteval — это, чем constexpr должен был быть изначально. Но… сделали как сделали…
Вот нафига нужна пометка, которая ни фига не гарантирует? Компилятор мог бы и так проверять inline функции на вычислимость в компайл-тайм и позволять их использовать там, где нам нужна константа.

Отлично. У вас нет constexpr. Вы объявили константу. Как теперь понять, она вычислится при компиляции или в процессе работы программы?

На основании того, что вы тут рассказваете про C++98, про то, как круто, когда функции можно вызывать в рантайме и всё такое прочее.

Давайте вы будете следить a) за своей речью и b) за тем, что вам говорят оппоненты.


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


Но совершенно не хотите понять того, что всё это не требует модификатора constexpr на функции.

Еще раз повторю вопрос: почему вы решили, что я не могут понять, что модификатор constexpr избыточен?


Я вполне себе могу представить, что если функция объявлена как inline и компилятор заглянет в ее код, то может решить, способен он ее вычислить в compile-time или нет. Собственно, CTFE в D наглядно показывает, что это возможно.


Тем не менее, я могу себе представить что у комитета и у компиляторостроителей были вполне себе веские причины обязать использовать constexpr.


Так что сделали и сделали. Лично мне принесли существенную пользу, за что комитету большое спасибо.


А вот ваша ярость не понятна от слова совсем.

Тем не менее, я могу себе представить что у комитета и у компиляторостроителей были вполне себе веские причины обязать использовать constexpr.
Так «вы можете себе представить» или «вы можете о них рассказать»?

Могу представить, что есть.

По поводу причин, фрагмент выступления Антона Полухина на CoreHard Autumn 2018: https://youtu.be/MXEgTYDnfJU?t=637


Собственно, там весь доклад посвящен особенностям constexpr в C++, если кто не видел, то можно найти минут 40 своего времени и ознакомится.

UFO just landed and posted this here

Директива constexpr означает, что функция может быть выполнена во время компиляции. Директива consteval — что функция должна быть выполнения во время компиляции. Если хотите, чтобы constexpr-функция была выполнена во время компиляции, то просто поместите результат её работы в constexpr-переменную.


Пример: вычисление квадратного корня. В случае, когда есть только consteval, придётся объявлять две функции: одну обычную, а одну — времени компиляции, и вызывать их явно.

Директива
constexpr
означает, что функция может быть выполнена во время компиляции.
Дык и любая другая функция тоже может быть выполнена во время компиляции. Даже если она не static и не inline. Если компилятор так захочет.

А если не захочет — так и constexpr-функция может отказаться выполняться в компайл-тайме.

Пример: вычисление квадратного корня. В случае, когда есть только consteval, придётся объявлять две функции: одну обычную, а одну — времени компиляции, и вызывать их явно.
Это великолепный пример. Потому что:
1. Про него говорят последгние лет 10.
2. Компилятор прекрасно извлекает корни в компайл-тайме.
3. Однако при этом использовать корень для создания массива — я не могу
Ну и кому стало легче от наличия constexpr? Без которого, заметьте, всё работало бы автоматически.

Вы ошибку компиляции-то видели? 
Всё прекрасно компилируется:





char buf[(int)sqrt(9)];

Дык и любая другая функция тоже может быть выполнена во время компиляции. Даже если она не static и не inline. Если компилятор так захочет.

Директива constexpr имеет тот же смысл, что и override.


Если функция объявлена как constexpr, то не заглядывая внутрь функции, вы можете быть уверенными, что она удовлетворяет ряду требований и может быть вычислена во время компиляции при определённых условиях.


Конечно, можно обойтись и без этой директивы — компилятор сам прекрасно разберётся, можно ли вычислить функцию в процессе компиляции. Проблема же в том, что тогда проверка на constexpr будет осуществляется не при определении функции, а при её использовании.

Вы ошибку компиляции-то видели?
Не обратил внимания. В GCC значит эти функции объявлены как constexpr (что, вообще говоря, нарушение стандарта, хотя и приятное). Clang так не умеет — хотя корень считает.

Если функция объявлена как constexpr, то не заглядывая внутрь функции, вы можете быть уверенными, что она удовлетворяет ряду требований и может быть вычислена во время компиляции при определённых условиях.
Вовсе не обязательно. Это просто пометка: «мамой клянусь, эту функцию, если приспичит, таки можно будет вызвать при некоторой комдинации входных аргументов». Компилятор этого проверить не может (строго говоря это вообще невозможно: проблема остановки, как всегда).

Директива constexpr имеет тот же смысл, что и override.
Нет, нет и нет. Вот если бы constexpr обозначал то, что означает consteval, а для вычисления функции во время компиляции не нужно было бы писать ничего — аналогия была бы полная. И это было бы хорошо. А так как это сделано сейчас — это голованя боль для разработчиков — и без каких-либо гарантий.

Проблема же в том, что тогда проверка на constexpr будет осуществляется не при определении функции, а при её использовании.
А сейчас где это осуществляется?

Вы на пример-то смотрели?
#include <iostream>

constexpr int this_is_stupid(bool stupid_arg = true) {
    if (stupid_arg) {
      std::cout << "WTH is this?" << std::endl;
    }
  return 42;
}

int main() {
    this_is_stupid();
}
Можно завести массив
char buf[this_is_stupid(false)];
а такой вот нельзя:
char buf[this_is_stupid(true)];
Так где у нас проверка, а?

Извините, но constexpr для функций — это ошибка дизайна. Такая же, как взаимодействие if constexr и static_assert. Вы действительно считаете, что вот это вот:
template<class T> struct dependent_false : std::false_type {};
template <typename T>
void f() {
     if constexpr (std::is_arithmetic_v<T>)
         // ...
     else
       static_assert(dependent_false<T>::value,
                     "Must be arithmetic");
}
Чем-то лучше, чем static_assert(false)? Ну бред же, честное слово!

Поймите, я не против C++, я понимаю, что при всех его недостатках это — часто наилучший выбор. Но многие вещи в нём это… не хочу материться, да…

Послушайте, ну объясните, наконец, что у вас лично за проблемы с constexpr. А то пока складывается ощущение, что это у вас что-то совсем личное и иррациональное. Кроме того, из ваших эмоциональных комментариев не понятно, что и как должно было быть сделано, чтобы раздражения у вас не вызывало.


Функция без constexpr — это указание компилятору, что пытаться считать еще в compile-time нельзя. Тогда как написав constexpr разработчик говорит "вот это я хочу иметь доступным и в compile-time".


То, что внутри constexpr может быть что-то, что воспрепятствует возможности вычислить функцию в compile-time… Ну, shit happens, что поделать (примеры подобного легко отыскиваются и в других местах в C++). В конце-концов вся идеология C++ такова, что компилятор верит в то, что программист знает, что делает.


Кстати, может быть в вашем примере проблема лишь в невнятной диагностике, которую выдают текущие версии gcc/clang. А вот vc++ 15.9.3 дает вполне себе понятное описание проблемы:


t2.cpp(10): error C2131: expression did not evaluate to a constant
t2.cpp(5): note: failure was caused by call of undefined function or one not declared 'constexpr'
t2.cpp(5): note: see usage of 'std::operator <<'
В конце-концов вся идеология C++ такова, что компилятор верит в то, что программист знает, что делает.
Ура-ура-ура. То есть вы всё-таки знаете принципы, на которых основан C++ немного.

Функция без constexpr — это указание компилятору, что пытаться считать еще в compile-time нельзя.
Да, но нет. Функция без constexpr — это просто функция без constexpr. Вот если бы для того, чтобы гарантировать, что функция не будет вызываться в тех контекстах, где нужно вычислять во время компиляции нужно было бы писать что-нибудь типа [[runtime]] — тогда да, можно было бы сказать, что это указание компилятору. А пустая последовательность символов ну никак не может быть указанием.

Тогда как написав constexpr разработчик говорит «вот это я хочу иметь доступным и в compile-time».
Нет. Написав constexpr вы убеждаете компилятор, который вам не хочет верить в том, что он должен пойти и посмотреть — а вдруг в данном конкретном случае функцию всё-таки можно вызвать во время компиляции? То есть это такой сертификат полноценности: да, я не тварь дрожащая, я право на твоё внимание имею!

Ну и как это соотносится с принципом, который вы сами же постулировали?
> Ура-ура-ура.

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

Пока из ваших комментариев мне так и не стало ясно ни что конкретно вам не нравится в constexpr, ни что вам бы понравилось.

А с учетом вашей манеры излагать мысли я вообще сомневаюсь, что вы это сможете сформулировать.

Нет, написав constexpr, вы заставляете компилятор в момент объявления функции проверить, удовлетворяет ли функциям определённым ограничениям. Это немного упрощает отладку кода.


Вы можете считать, что constexpr — лишнее слово, но когда constexpr только появился, требования к функциям, которые могут быть вычислены в процессе компиляции, были очень жёсткие. Сейчас эти требования постепенно смягчаются, но constexpr уже никуда не денется. Это станет legacy, с которым придётся мириться.

Вы можете считать, что constexpr — лишнее слово, но когда constexpr только появился, требования к функциям, которые могут быть вычислены в процессе компиляции, были очень жёсткие. Сейчас эти требования постепенно смягчаются, но constexpr уже никуда не денется.
А с этим я как раз не спорю. Типичный случай ситуации «хотели как лучше, а получилось как всегда». Когда во время компиляции могли исполняться только функции очень ограниченного вида и любой static_assert, вставленный в такую функцию делал невозможным использование её во время компиляции — эта пометка имела смысл. Чтобы не было сюрпризов, когда Вася добавил в функцию простую проверку — а у Пети программа перестала собираться.

Когда же в языке этих проблем нет (в C++20, вроде как, даже constexpr new будет) — то эта пометка, ничего, в сущности, всё равно не гарантирующая, выглядит, как минимум, странно.

Это станет legacy, с которым придётся мириться.
Вот это-то и огорчает.

Поскольку совместимость ломать нельзя — то подобную грязь из языка крайне сложно вычистить. Может быть после появления модулей можно будет заняться, наконец, расчисткой этого всего. Сейчас это невозможно, так как любой заголовочный файл должен одинаково компилироваться в любом компоненте программы. Если заголовочных файлов не будет — то можно будет разрешить в новых модулях constexpr не писать (просто считать, что все функции, к которым можно добавить constexpr и получить после этого валидную программу — уже неявно эту пометку имеют).
UFO just landed and posted this here
UFO just landed and posted this here
Ага, а играть в хоккей надо начинать с художественной гимнастики. Что тренируешь, то и развивается, лучше разбираться с тем, что нужно, чем тратить тоже время на то, что не пригодиться.
Когда изучал язык С, а потом переходишь к С++, то реально ценишь возможности, которые предоставляет С++. Да и какие-то базовые понятия все-таки проще изучать в С: работа с файлами, многопоточность и т.д.
У меня первым языком программирования в институте был С, о чем ни чуть не жалею.

На мой взгляд, до начала изучения С++ будет неплохо знать хоть какой-то язык программирования.
Статья неплохая и интересная. А вот комментаторы странные. Люди, почему Вы спорите чей язык лучше? Ну серьёзно? Кто любит Delphi, пусть пишет на Delphi, кто любит C# пусть пишет на C#, кто любит С++,… ну вы принцип поняли. Если Вам жизненно важно заставить другого человека писать не на том, что ему нравится, а на том, что нравится Вам, то, поздравляю, у Вас психологические проблемы. Сходите к психотерапевту.

Ну серьёзно. Объясните мне кто-нибудь: «Почему так обязательно заставлять другого принимать твою точку зрения, если тебе от этого ничего не будет?» Ладно, если бы мне платили за агитацию некоторого языка, то я бы изо всех сил агитировал бы. Но за так… Зачем силы-то тратить?
А зачем на Хабр заходить и вообще чего-то писать?

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

А. Это не знаю. У меня как раз основной язык — C++, но я очень хорошо понимаю, что многие вещи в нём сделаны неразумно. По крайней мере если смотреть из сегодняшнего дня…
У меня основного нет. Я работаю на: C, С++, C#, Delphi, VHDL, Verilog, Java. И это только те, на которых я реально работаю в данный момент. Работаю я на них всех не от того, что я так крут (скорее всего это не так), а от того, что все они чем-то хороши. В зависимости от задачи лучше подходит тот или иной. Можно, конечно, их по сравнивать. Но зачем из кожи вон лезть, что бы что-то доказать?
В некоторых случаях и выбора-то нет. Verilog на C# сложно заменить.
А мне говорили, что С++ в этом веке сделали более удобным…
Вот такой вот парадокс: почитаешь форумы в Интернетах и складывается ощущение, что C++ и раньше был говном, а уж сейчас-то его точно сделали только хуже, развитие языка давным-давно свернуло не туда, сам язык стал настолько сложным, что только интеллектуалы с IQ выше 170 смогут написать хотя бы HelloWorld, в комитете заседают оторванные от реальности фрики, поэтому комитет мало того, что добавляет в язык то, что никому не нужно, так и делает это максимально корявым и неестественным образом.

А на практике с каждым новым стандартом писать на С++ становится и проще, и удобнее.

Яркий пример — тот самый constexpr, на который выше вылили столько дерьма и даже обозвали «интеллектуальная мастурбация». Хотя в обычной работе ты просто его используешь и радуешься, что теперь такая фича в языке есть.
Хотя в обычной работе ты просто его используешь и радуешься, что теперь такая фича в языке есть.

Вы сравниваете два состояния: "до того как ввели constexpr" и "после того как ввели constexpr". А надо — три: "до того как ввели constexpr", "после того как ввели constexpr" и "что было бы, если бы constexpr ввели по-нормальному".

Во-первых, вы забываете, что у истории нет сослагательного наклонения.

Во-вторых, вы, забываете, что C++ — это инструмент, который нужен здесь и сейчас (хотя, как обычно, хороший инструмент нужен был еще вчера). Мне, при разработке софта, важно то, что инструмент позволяет мне сделать. А фантазии о том, что могло бы быть, я оставлю для форумных болтунов.

В-третьих, пока в этом обсуждении никто еще не показал, как следовало бы сделать «по-нормальному». Яркие фразы вроде «интеллектуальная мастурбация» в расчет не принимаются.
Вам об этом сказали несколько раз, прямым текстом. Могу повторить:

Вариант 1. Разрешить в constexpr-выражениях вызовы любых inline-функций.
Вариант 2. Запретить в constexpr-функциях делать то, что помешает использовать её в constexpr-контексте.
Вариант 1. Разрешить в constexpr-выражениях вызовы любых inline-функций.

Одна из претензий к constexpr-функциям в том, что там можно делать все, что угодно. И пример был приведен с обращением к std::cout-у. Если уж для constexpr-функций компилятор не может помешать делать такие вещи, то что уж говорить про обычные inline-функции.


Так что тут либо крестик снимите, либо...


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


Вариант 2. Запретить в constexpr-функциях делать то, что помешает использовать её в constexpr-контексте.

Вы точно хотели сказать "использовать её в constexpr-контексте" или же хотели написать "использовать её не в constexpr-контексте"?

Если constexpr-функции нельзя было использовать вне constexpr-контекста, то ценность самих constexpr-функций и удобство работы с ними сильно снизилось бы.

Во-первых, если мне нужен расчет одной и той же величины и в compile-time, и в run-time, то нужно было бы делать две одинаковых функции. Только одна с пометкой constexpr, вторая — без.

Во-вторых, как отлаживать constexpr, если вызывать их можно только в compile-time? Пока constexpr-функция может вызываться в run-time, я могу отлаживать ее обычными средствами и писать для нее unit-тесты.
А кто предлагает запретить использовать constexpr-функции вне constexpr-контекста? Не читайте в моих комментариях того, чего в них нет.
Если на какой-то момент в constexpr-функции мне потребуется, скажем, отладочная печать, то мне на это время придется убирать отметку constexpr?
Ух ты, первое возражение по делу! Вам следовало написать это три дня назад...

Можно скопировать функцию, убрав у копии constexpr. Лично я бы предпочел делать вот так, чем иметь в языке непроверенные компилятором constexpr-функции. Всё-таки отладочная печать — это уже крайнее средство, я ей не каждый день пользуюсь.
Вам следовало написать это три дня назад...

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


Кстати, еще лучше было бы список претензий к constexpr из практики, а не из высказываний вида "по-моему, это сделано через… опу".


Лично я бы предпочел делать вот так

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


Даже если вернуться к примеру с std::cout в constexpr-функции. По сути, вот такой же пример:


constexpr size_t dimension(bool flag) { return flag ? 64u : 0u; }

int main() {
   char a[dimension(true)];
}

Здесь все формальности по поводу constexpr-сности соблюдены, но поменяв значение параметра для dimension вы получаете ошибку компиляции.


И, с точки зрения практики, мне без разницы, почему вызванная мной constexpr-функция ломает компиляцию: потому, что в ней вызывается std::cout, или потому, что она возвращает недопустимое для моего контекста значение.

UFO just landed and posted this here
Sign up to leave a comment.

Articles