Pull to refresh

Comments 130

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

Перепроверил по черновику предложения

  • `dealias(x)` отбрасывает все алиасы любой вложенности.

  • Трюк с получением имени через алиас на самом деле не сработает т.к. рекомендуется отбрасывать информацию об алиасах в `type_of`. Подправил статью

Тогда отвалится ассерт

static_assert(dealias(type_of(^^name)) == ^^std::string);

Так как std::string тоже алиас.

В зависимости от того, под какой процессор скомпилировано ваше приложение, результат floatv::size() будет отличаться. Если архитектура поддерживает эффективную работу с 8 float одновременно, то будет 8.

А если размер векторного регистра в compile time неизвестен (скажем, ARM SVE) - будет ли size() вычисляться динамически (и насколько хорошо в целом std::simd такое поддерживает) ?

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

В том же SVE (или AVX512) хвосты нормально обрабатываются векторными инструкциями с помощью предикатов/масок. Можно ли с std::simd написать код без явного вспомогательного скалярного цикла, чтобы дать компилятору возможность этим воспользоваться?

А если размер векторного регистра в compile time неизвестен (скажем, ARM SVE) - будет ли size() вычисляться динамически (и насколько хорошо в целом std::simd такое поддерживает) ?

Вот в RISC-V аналогичная ситуация. Там размер регистра получается вызовом рантайм команды. В текущей версии стандарта size() всегда известен на этапе компиляции

Можно ли с std::simd написать код без явного вспомогательного скалярного цикла, чтобы дать компилятору возможность этим воспользоваться?

Думаю да. Как минимум можно проинициализировать std::simd нулями, докопировать оставшуюся часть в simd переменную и выполнить команду. Маски тоже поддерживаются

Вот в RISC-V аналогичная ситуация.

Странно, что народ (разрабочики ISA и вендоры железа) не суетится. Насчёт распространённости чипов с RVV не в курсе (знаю только, что в природе они есть), но SVE и на серверах из Top500 есть, и на телефонах появляется.

Как минимум можно проинициализировать std::simd нулями, докопировать оставшуюся часть в simd переменную и выполнить команду.

Всё равно какие то явные действия в исходнике. В том же SVE стандартный паттерн в бинарнике для обработки массива произвольной длины - один цикл без дополнительного кода, предикаты автоматом отсекают ненужные эдементы на последней операции. Насколько понял, можно написать цикл с явными масками и на std::simd - но он может привести к неоптимальному бинарнику на SSE или NEON. Хотелось бы чего то более высокоуровневого - просто написать цикл и дать реализации возможность обработать хвост естественным для целевой ISA способом.

Вот в RISC-V аналогичная ситуация.

Странно, что народ (разрабочики ISA и вендоры железа) не суетится. Насчёт распространённости чипов с RVV не в курсе (знаю только, что в природе они есть), но SVE и на серверах из Top500 есть, и на телефонах появляется.

Динамический размер вектора это фича RVV, причем одна из важнейших. Она позволяет существенно упросить обработку хвостов. Смотрите доклад Константина Владимирова "Ни на что не похожая векторизация и цикловые оптимизации в RISC-V". В LLVM поддержка RVV постепенно внедряется, без интринсиков, компилятор сам распознает паттерны и генерирует правильный код для RVV.

PS: На счет "ни на что не похожая", я конечно усомнился бы, IMHO что-то подобное было в машинах Cray и CDC.

Я уже привёл пример SVE от ARM, так что переменная длина вектора совсем не экзотика. Да, на простых циклах gcc и clang генерируют векторный код сами, достаточно задать правильные ключи для архитектуры и оптимизации, но как только мы уходим в сторону от однородной обработки, начинаются проблемы - тут и нужен std::simd, чтобы написать вручную кроссплатформенный векторный код.

Всё-таки SVE с поддержкой векторов больше 128 бит очень мало где можно встретить. Если не ошибаюсь в последнем кремнии от Apple SVE есть только как часть SME.

Также один из факторов - возможность создания массивов simd векторов, что возможно только с известным во время компиляции массивом.

в последнем кремнии от Apple SVE есть только как часть SME

Судя по постам в сети и патчу в llvm там есть SME, но нет SVE. Но в процессорах на базе Neoverse N2/V2 (последние Гравитоны, NVIDIA GRACE) должно быть.

Спасибо за уточнение и ссылку! Да, как то запутали терминологию - получается, что формально в фичах процессора выставлен бит SME и не выставлен SVE, но инструкции типа FMLA использовать можно. При этом ещё (возвращаясь к вопросу о длине вектора) "SVL is independent of SVE Vector length (referred to as VL which is the vector length when not in Streaming SVE mode)" отсюда, т.е. даже на конкретном процессоре размер SVE регистра может меняться в зависимости от режима.

текущее предложение по рефлексии не позволяет работать с атрибутами.

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

^^ выглядит ужасно. Символ ` (обратный апостроф) не используется в С++ могли бы его использовать.

Опережающее описание для вложенных классов сделают хоть в каком-то столетии?

class Foo;

class Foo::Bar; // когда это будет работать?

void test(Foo::Bar*);

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

Тут вопрос в переносимости фичи. С атрибутами хотят позволить работать, но это одно из самых различающихся мест в фронтенде имеющихся компиляторах. Поэтому выбор был между "рефлексией в C++26 без атриботов" и "рефлексией с атрибутами незивестно когда"

^^ выглядит ужасно. Символ ` (обратный апостроф) не используется в С++ могли бы его использовать.

На моём экране он практически не отличим от одинарной кавычки :(

class Foo::Bar; // когда это будет работать?

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

Лучше что-нибудь из @ или $ выбрали бы.

Когда вы напишите предложение и реализуете прототип в компиляторе.

Совет из разряда обратитесь в спортлото. Комитет не заинтересован в развитии языка, писать бесполезно. И ожидаемый через "пару" лет выход неполноценного огрызка рефлексии это доказывает. Напомню, что рефлексия является мейнстримом в языках программирования уже более 20 лет (27 лет назад реализована в java) и формулировки мы хотим успеть выпустить в 26-м звучат особенно издевательски.

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

Комитет не заинтересован в развитии языка, писать бесполезно.

А вы пробовали? Реализовали прототип для forward declarations вложенных классов хотя бы в одном компиляторе, выкатили на его основе пропозал? Где можно об этом почитать?

Совет из разряда обратитесь в спортлото.

Комитет занимается бюрократией, а не разработкой. Нет пропозала — нет внедрения.

Напомню, что рефлексия является мейнстримом в языках программирования уже более 20 лет

А метапрограммирование ещё старше (реализовано 33 года назад в C++), но почему-то в этих ваших жавах до сих пор какой-то неполноценный огрызок. Видимо, оракл не заинтересованы в развитии языка.

Возможно форк языка со сменой модели разработки

Отдать корпорации? И кому это надо?

А метапрограммирование ещё старше (реализовано 33 года назад в C++), но почему-то в этих ваших жавах до сих пор какой-то неполноценный огрызок. Видимо, оракл не заинтересованы в развитии языка.

Ето да. Меня лично, к слову, больше всего расстраивает отсутствие в яве аналога вариативных шаблонов из плюсов (т.е. грубо говоря возможности передачи произвольного количества аргументов типобезопасным образом), в результате чего приходится иметь дело примерно вот с таким. Впрочем, в прямом наследнике явы котлине ситуация ничуть не лучше.

Пропозал по этой проблеме (вложенных классов) был еще в 2013 году. С тех пор воз и ныне там.

Метапрограммирование в джаве и шарпе сделано на недостижимой для С++ высоте. Вы можете создавать классы на лету и генерировать байт код который JIT превратит в машинный. В С++ метапрограммирование ограничено временем компиляции, что делает его неприменимым во множестве сценариев. Вот сейчас лепят очередное ноу-хау - статическую рефлексию. А кому эта статическая рефлексия нужна? Опять возникнет миллион костылей для организации динамической рефлексии на базе статической. И когда говорят о метапрограммировании в С++ мне становится смешно. Посмотрите хотя бы на реализацию костыля из boost который преобразует структуру в кортеж. Под капотом у него сгенерированный скриптом код для ограниченного количества сценариев. В чем смысл этого метапрограммирования если оно постоянно упирается в нереализованные возможности и баги языка которые принципиально не исправляются?

Пропозал по этой проблеме (вложенных классов) был еще в 2013 году. С тех пор воз и ныне там.

Так и в чём там проблема? Комитет его игнорит, или комитет дал фидбек, а автор(ы) просто забил(и)?

Вы можете создавать классы на лету

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

А кому эта статическая рефлексия нужна?

Вы там ниже сами привели пример, отвечающий на этот вопрос (структуры->кортежи).

в нереализованные возможности

Начните в своих шарпах и жавах со специализаций и родов выше * -> *, а там посмотрим.

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

Я наверное не понимаю вашего вопроса. Но все равно попробую ответить :)

Зачем создавать в рантайме - потому что в компайл-тайме всего выражения еще нет, например. В с++ есть аналог - это expression tree, но он ограничен статическими выражениями, но при этом позволяет создавать код, который нам нужен и как нам нужно . В c# позволяет сделать тоже самое, но не ограничивает вас только статическими выражениями.

А классы в рантайме для этого надо создавать, чтобы в сгенерированном коде не было дополнительных накладных расходов и он был создан только под конкретную задачу. Хороший пример производительного orm, который использует эту технологию - linq2db. Я бы посоветовал ознакомиться, но вы вряд ли будете этим заниматься :)

Зачем создавать в рантайме - потому что в компайл-тайме всего выражения еще нет, например.

А приведите пожалуйста такой пример

классический sql inject видимо))
Хотя я сталкиваюсь зачастую с тем, что люди из других языков даже не представляют, что можно было сделать на компиляции

linq2db

На первый взгляд (посмотрел первые насколько туториалов) весь функционал спокойно реализуется через compile time рефлексию (получение имён полей, доступ к полям, маппинг из базы на структуру... большинство этого уже сейчас можно делать с помощью Boost.PFR и мы пользуемся подобным в userver при работе с базами данных).

Приведите пожалуйста пример, где нужна именно рантайм рефлексия

Я ознакомился с Boost.PFR и вы правы, в том, что большая часть использований может быть им покрыта. Если мы говорим про POD/POCO типы.

Но, тот же linq2db активно пользуется атрибутивной разметкой и прочей метаинформацией, которая свзяна с полями и свойствами. Насколько я понимаю, такого сделать с помощью Boost.PFR нельзя и опять же, имхо, без поддержки со стороны языка невозможно. Можно придумать обходные варианты с fluent mapping или builders (и в новых версиях linq2db оно тоже доступно).

Также я посмотрел что Boost.PFR использует loophole, что принципиально снимает ограничение на использование типов из других сборок/DLL.

Но даже у вас в userver запросы пишутся вручную (я ознакомился бегло, рад буду ошибиться). Что является источником ошибок (привет sql injection), хотя, насколько я понял, вы стараетесь от этого защищаться.

LINQ и различные библиотеки (linq2db, EntityFramework) и другие снимают с меня как с программиста необходимость писать SQL запросы руками (при этом linq2db генерирует очень качественные запросы), готовя за меня prepared statements и передавая туда параметры (с проверкой типов на уровне С#). т. е. я принципиально не могу написать неправильный запрос и забыть передать в него параметры или передать не те.

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

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

Судя по тому, что вы делаете в Boost.PFR и в целом куда движется язык аналогичную функциональность (с ограничениями) можно сделать и на С++, но выглядит это все очень дорого и нужны эксперты‑энтузиасты, вроде вас для этого. Тот же С# дает более простые инструменты.

Спасибо!

В userver запросы тоже превращаются в prepared statements и параметры шлются отдельно от текста запроса в бинарном виде, что избавляет от sql injection. Но да, автогенерилки запросов у нас нет (имхо, написать на SQL запрос проще чем учить специфичный для библиотеки DSL; но пользователям фреймворка хочется такой DSL иметь)

Например, набор фильтров, которые пользователь создает через UI или предикаты бизнес-логики, которые мы загружаем из отдельной сборки.

Это решается через std::function<bool(const data&)>, или у вас какой-то особо сложный случай? Дайте пожалуйста побольше подробностей.

Разговор же был не про то что можно сделать через std::function или обычный делегат в c#, а про то что для этого можно сгенерировать специализированный код в рантайме, который будет эффективнее списка предикатов.

На второй вопрос выше я попробую ответить позже - несколько последних лет я не пользуюсь c#.

Посмотрите на Факторио. Эта игра написана на С++? Нет, она написана на Луа. Теперь посмотрите на Java - скриптинг можно делать прямо на яве, компилируя в рантайме, или на скриптовых динамических языках типа groovy. При этом, во-первых, разработчик скриптов получает нормальный взрослый язык программирования (луа, всё же, жуткая поделка), а во-вторых, рантайм компиляция генерит код в JIT инфраструктуру JVM, то есть на выходе получается код того же уровня оптимизации, что и у самой явы.

Так и в чём там проблема?

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

И какие у это реальные области применения?

Очевидно же что все области где правила или логика определяются пользователем и при этом требуется высокая производительность.

Вы там ниже сами привели пример, отвечающий на этот вопрос (структуры->кортежи).

Т.е. нигде. Потому что задачи преобразования структуры в кортеж не существует.

Начните в своих шарпах и жавах со специализаций и родов выше * -> *, а там посмотрим.

Вы слишком переоцениваете ценность этих "концепций" и "парадигм" которые нагородили в современном С++. Я пишу на С++ и шарпе и еще ни разу в шарпе мне не потребовалась функциональность из С++.

Я пишу на С++ и шарпе и еще ни разу в шарпе мне не потребовалась функциональность из С++.

Вы еще скажите, что делаете на C# тоже самое, что делаете на C++.

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

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

Легко представить, что на C# вы берете JSON-ы и перегоняете их в БД, или в обратном порядке.

Тогда как на C++ вы занимаетесь анализом видеопотоков или deep packet inspection.

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

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

Легко представить

Мой совет всем С++ разработчикам поменьше "легко представлять" и побольше знакомится с реальностью.

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

Мой совет всем С++ разработчикам поменьше "легко представлять" и побольше знакомится с реальностью.

Просто удивительно как такое светило снизошло своей милостью до общения с плебсом.

Вам просто не надо занимать позицию интеллектуального превосходства заявляя, что на С++ задачи огого, а в шарпе люди только json из базы перекладывают. Я использую шарп и С++ и многие шарп-задачи делать на С++ не просто нецелесообразно, а просто физически нельзя т.к. в С++ не существует такой функциональности.

Вам просто не надо занимать позицию интеллектуального превосходства заявляя, что на С++ задачи огого, а в шарпе люди только json из базы перекладывают.

А теперь покажите пальцем, где бы я говорил и про "на C++ задачи огого", и, особенно, про "интеллектуального превосходства".

Что-то вы себе выдумали, а потом в этом обвинили собеседника.

Речь как раз и шла о том, что решая разные задачи на разных языках вам нужны разные возможности. Поэтому ничего удивительного в том, что в C# вам не нужны фичи C++.

Зачем из таких очевидных вещей устраивать цирк с "интеллектуальным превосходством" -- хз.

А теперь покажите пальцем,

Вот показываю пальцем:

Легко представить, что на C# вы берете JSON-ы и перегоняете их в БД, или в обратном порядке.

Тогда как на C++ вы занимаетесь анализом видеопотоков или deep packet inspection.

Прекрасно, где здесь высказывалась оценка что одно легче/сложнее лучше/хуже другого?

А почему вы для C# выбираете какие-то тривиальные примеры? А для С++ какие-то потоки и dpi.

Если бы вы написали: "легко представить, что на С# вы используете Unity, а на С++ Unreal" это было бы равноценно.

А почему вы для C# выбираете какие-то тривиальные примеры?

Кто вам сказал, что работа с JSON и БД -- это тривиально? Если это в вашей реальности так, то это вовсе не означает, что это мной подразумевалось. Т.е. речь о ваших домыслах.

"легко представить, что на С# вы используете Unity, а на С++ Unreal"

Не написал поскольку не имел дел ни с тем, ни с другим.

на С++ можно написать С#, значит нет такой задачи на C#, которую нельзя сделать на С++

Я когда впервые в шарпе сгенерировал класс на лету и оценил какой прирост производительности это дало, то сразу же осознал насколько нелепы претензии С++ на всю сферу высокой производительности

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

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

А чего заминусили-то? Так-то мужик правду сказал.

Т.е. нигде

С задачей сериализации вы, как я понимаю, в жизни ни разу не сталкивались?

Вы слишком переоцениваете ценность этих "концепций" и "парадигм" которые нагородили в современном С++. Я пишу на С++ и шарпе и еще ни разу в шарпе мне не потребовалась функциональность из С++.

А я пишу на с++ и совсем немного на шарпе, и ещё ни разу мне в с++ не потребовалась функциональность шарпа.

Лично я уже более 10 лет в С++ сериализую данные используя доморощенную рефлексию времени выполнения. На половинчатые решения которые работают только для самых простейших случаев я не полагаюсь.

А я пишу на с++ и совсем немного на шарпе, и ещё ни разу мне в с++ не потребовалась функциональность шарпа.

Думаю что как минимум вам не хватает интерполяции строк

Первые 10 лет в этом бизнесе писал на спп, вторые 10 лет шарп и спп, третий десяток начал снова на спп.

Итак маленький список

1)шаблоны.даже без вариадиков их сравнение с дженериками вызывало тоску и плач

2)const модификаторы методов

3).* и - >*

4)кастомный захват в лямбдах

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

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

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


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

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

Рефлексия нужна, она позволяет без костылей делать многие вещи.

2. чтобы делать на рантайме классы нужен на рантайме компилятор, очевидная вещь

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

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

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

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

Алиас чего там нельзя написать, и почему это так важно? Вопрос от мальчика Димы, программиста на Си с 1984 года и Си++ с 1987, перешедшего на Яву с 2004 и с тех пор не понимающего, зачем писать на си и его диалектах кроме как в целях самоистязания.

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

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3394r0.html
Но в 26 этого точно не будет, и неизвестно пройдет ли оно дальше (R0 все же)

Да и вообще они рефлексию для 26 сильно урезали, иначе она бы уехала до 29 или дальше.

Опережающее описание для вложенных классов сделают хоть в каком-то столетии?

Скорее всего нет. Чистота вскидку: есть проблемы с областью видимости. Путь в классе A есть вложенный класс B в private области. Есть единица трансляции, в которой указан этот forward declaration. Ну и используется: ̀A::B*̀. По идее, компиль должен ругнуться на нарушение области видимости, но не может.

А что вы думаете?

Когда стандартизаторы пытаются подсчитать биты (bit) в байте (byte), но при этом именуя его количество как CHAR_BIT (char-bit), то только у меня одного возникает когнитивный диссонанс? Если уж условным стандартизаторам хочется задать размер char - пожалуйста, но зачем сюда приплетать байт? Тем более, что функции наподобие memset явно намекают, что условный char может распухать до размера int (и всё равно sizeof(char) == 1). Тогда зачем ломать то, что работает?

С другой стороны, есть типы с явно заявленным количеством битов: int8_t и т.д. - почему не пользоваться тем, что уже есть? Назовите uint8_t как байт, если сильно хочется, и будет вам счастье!

А ещё есть третья сторона: в стандартах на телекоммуникационные протоколы (а там данные считают по битам и может, например, быть передано 2 бита в одном канале) используется термин: октет битов. И этот термин явно намекает, что битов в октете явно восемь. Не десять, не семь, и не три-с-четвертью.

И с таких позиций может вообще уйти от понятия байта, хотя бы в стандарте?

Например:

  • есть октет битов - там восемь бит;

  • есть char - там CHAR_BIT битов (минимум столько-то, максимум такой-то) и количество зависит от аппаратуры;

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

Чем плох вариант?

Когда стандартизаторы пытаются подсчитать биты (bit) в байте (byte), но при этом именуя его количество как CHAR_BIT (char-bit), то только у меня одного возникает когнитивный диссонанс?

Возможно, у вас и у тех, кто пришли в C++ минуя чистый Си.

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

Ну нет, байт - это минимальная адресуемая область памяти. А то что вы написали, это машинное слово, и оно давным-давно уже не 8 бит.

Семейство микроконтроллеров для силовой электроники TI C2000 имеет 16-битный байт, да.
Стандартная библиотека реализует <string.h> поверх 16-битных ячеек памяти, хотя у процессора есть возможность работать со "старшей" или "младшей" половинкой ячейки памяти - это ломает ABI, поэтому строки в С2000 жрут в два раза больше, чем на других архитектурах при довольно скромных ресурсах младших моделей

А там имеется поддержка C++ компилятора, или только C?

Последний раз когда интересовался - застопорились на поддержке С++03. Для армов ушли на LLVM, а со своей экзотикой ничего не поделать.
Но в DSP плюсы нужны, как собаке пятая нога, там выхлоп компилятора глазами перепроверяют, чем меньше абстракций, тем лучше

Конпелятор C/C++ для TI C7000 таки умеет в C++14 (со своими особенностями, но все-таки).

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

С другой стороны, есть типы с явно заявленным количеством битов: int8_t и т.д. - почему не пользоваться тем, что уже есть? Назовите uint8_t как байт, если сильно хочется, и будет вам счастье!

Увы, но на тех платформах, где в байте не 8 бит, никакого int8_t не существует. Типы int_fast8_t и int_least8_t есть в стандарте не просто так...

У того, что в стандарте закрепят byte == 8bit есть ещё неприятный момент для таких плформ. std::byte и char типы на этих платформах перестанут подпадать под стандарт, и начнутся проблемы с placement new и использовании std::byte в принципе :(

С другой стороны, какова вероятность, что для этих железок вообще будет поддержан компилятор с C++26?
В embedded много железок со старыми стандартами C и C++.

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

Ну вот так исторически сложилось, что байт в Си и С++ называется char

По-моему, это НЕ исторически сложилось. Например, в ассемблере (который можно считать в какой-то степени предшественником С), там всё хорошо с байтами. И есть слова и двойные слова. И обычно, когда говорят про байт/слово/двойное слово подразумевают беззнаковое число. В языке C тип char скорее знаковый (да, кэп, можно поменять). И знаковость это ещё один гвоздик в диссонанс.

По-моему, в языке C хотели именно char как единичный символ строки и так как он тогда хорошо ложился на байт (вспомним 7-битные и 8-битные терминалы), то его и ввели в C.

Про "int8_t не существует" просто приведу два примера (с меньшей стороны и с большей стороны):

  • в ассемблере, когда были 16-битные регистры (да, ax и int 21h forever) результат умножения выдавался в регистровую пару dx:ax как 32-битное число. И никого это не напрягало, всё работало. Подчеркну: ЭТО АССЕМБЛЕР, КАРЛ! Сишник в любом случае может ещё добавить возможностей;

  • недавно разбирался с кодогенерацией под Arm и там 8-битную переменную затолкали в 32-битный регистр (и старшие биты были нулями) и всё работало. Опять же никого не напрягало.

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

Также добавлю, что с адресацией всего этого зоопарка весь вопрос только в силе "натягивания совы на глобус". Например недавно столкнулся: (современный компилятор C++) в коде есть массив 2-байтных чисел и он не выровнен на границу 2-х байт. Если вы попробуйте побегать/покопировать элементики такого массива, то процесс просто крэшнется. Это такой бонус за переход на более новую ОС/компилятор. И ничего, "всё штатно", "улыбаемся и машем", переписываем/дочищаем код.

После этого тема упаковки 8-битных "байтиков" в более длинные блоки (особенно, если адресация будет как-бы байтов: +1 к адресу указывает на следующий длинный блок, в котором используется всего 8 бит) - вообще не проблема.

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

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

В ассемблерах тоже с терминологией не всё гладко на самом деле.

Байт - это о разрядной сетке. Слово - минимально адресуемая память (если не путаю). На всяких x86 по идее 1 слово должно быть равно 1 байту. Когда и почему это стало не так, честно говоря, не знаю.

По поводу "исторически сложилось" - это не в целом всё IT исторически сложилось, а исторически сложилось в C++, как наследие C, где не было такого типа данных, как байт. По сему, в качестве этого самого байта использовали char.

Путаете, минимально адресуемая область памяти - это как раз байт. А слово - это, если говорить упрощённо, разрядность регистров процессора. На "всяких x86" слово не было равно байту с самого первого же процессора.

Мне больше режет глаз фигурирование понятия "бит". Но это больше вопрос терминологии, что по идее в стандарте соблюдаться должно.

А что вы думаете?

Интересно, а в комитете всех устраивает использование оператора "баян", т.е. [: :] в рефлексии?
ИМХО, так это чуть ли не максимально нечитаемая комбинация из тех, что можно было придумать. Двоеточия на фоне квадратных скобочек не считываются :(

А что бы вы предложили использовать вместо [: :] ?

Да что угодно, если уже все это заключается в квадратные скобки: [~ ~], [@ @], [# #], [* *]. Можно было бы, наверное, и ^ задействовать, типа [^ ^], раз уж "крышечка" просочилась в рефлексию, и оно бы было бы даже логичнее, но тогда, боюсь, кто-то напишет [^^^something^].

Лично я бы выбирал между:
[@ template_arguments_of(type_of(^^name))[1] @]
[# template_arguments_of(type_of(^^name))[1] #]
но мои вкусы несколько специфичны ;)

Немного в сторону добавлю вопрос - а почему вообще баяны и моноброви добавляют, вместо четкого и понятного словестного обозначения? Ключевое слово или как-то еще?

Боюсь, вы задаете вопрос не тому человеку :)

Что касается моноброви, то, как по мне, то это лучше, чем изначальное использование одинарной "крышки". Т.е. планировалось ^Type, сейчас хотя бы ^^Type, что и заметно лучше, и не вызывает диссонанса с уже имеющимся в C++ operator^.

Что касается [: :], то вроде бы это распространенная тема в различных template engines, когда есть специальные маркеры, отличающие текст самого шаблона, от места подстановки генерируемого текста. И это удобно. Код вида:

[: template_arguments_of(type_of(^^name))[1] :]

хоть и неважно читается из-за плохого выбора набора символов, но все-таки гораздо лучше условного:

co_degen_begin template_arguments_of(type_of(co_reflection_of name))[1] co_degen_end

PS. И да, ужас в виде co_await и co_return -- это еще один пример того, что у комитета со вкусом как-то не очень :)))

почему вообще баяны и моноброви добавляют, вместо четкого и понятного словестного обозначения? Ключевое слово или как-то еще?

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

Хочу спросить про constexpr. Сам я этим не пользуюсь, как-то не возникает потребности, но интересуюсь языками программирования как таковыми.

Оправдал ли себя такой подход, что нужно каждую функцию явно помечать как constexpr? Или лучше как в zig, где ничего помечать не требуется, и достаточно поставить comptime блок и все что в нем вызывается - происходит во время компиляции, а если компилятор не может что-то конкретное - он просто выдает ошибку на это место?

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

а есть предложения constexpr println ? Чтобы просто выводило в impl defined output компилятора?

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

К сожалению, маркер constexpr как обещание не очень работает: вполне себе можно функцию, никак не работающую в comp-time, пометить constexpr, с точки зрения стандарта это вроде нормально, но точно я не помню. Это сделано для тех случаев, когда при одних вводных, функция работает в constexpr-"режиме", а при других в рантайме.

Возможно, это разрешимо с помощью consteval, но уже не помню. Типа такого:

consteval ... c_fn(...) {...}
constexpr fn(...)
{
  if constexpr
  {
    ...
    c_fn(...)
    ...
  }
  else
  {
    ...
  }
}

Ну, точно не знаю, наспех придумал.

Короче говоря, комитет очень сильно влюблён в свои возможности принимать самые странные решения.

Ну тут по-хорошему надо в исторической динамике, так сказать, все рассматривать. Когда constexpr только появился, были довольно жесткие ограничения на то, что constexpr функции могут делать, а чего не могут (например, в C++11 такие функции могли иметь только простейший вид), и это все проверялось компилятором, т.е. это действительно было нечто вроде обещания, что функция 100% может выполняться в constant-evaluated context. В более поздних стандартах нравы "помягчели", а constexpr функции стали уметь больше, компилятор все еще проверяет внутренности constexpr функций, но, если функция не выполняется в constant-evaluated context, то он ограничивается предупреждением (в constant-evaluated context будет ошибка).

Возможно, это разрешимо с помощью consteval, но уже не помню. Типа такого:

Для этого существует if consteval, if constexpr - это про другое.

Короче говоря, комитет очень сильно влюблён в свои возможности принимать самые странные решения.

Задним умом все крепки :)

про if consteval — в моменте перепутал, спасибо))

constexpr не особо часто приходится использовать, поэтому многие особенности всё же не помню, да и вспомнить всё же как будто проще будет лишь в момент необходимости:)

Задним умом все крепки :)

Было бы тру, если бы не явное отставание с простыми вещами, которые достаточно посмотреть в других языках, с одновременным внедрением того, что нужно 3.5 разработчикам или внедрением в таком виде, что ещё будет переделано не раз, а то и заменено альтернативой

с простыми вещами, которые достаточно посмотреть в других языках

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

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

Но вернёмся к сути.

  1. Кто мешал "изобрести" уже устаревший std::format до C++20? Эталонная имплементация в PHP существует уже 20 лет. Ах, да... пропозал никто не написал )

  2. Зачем в последних стандартах появилась куча удобных хелперных функций, которые уже те же лет 20 есть в других языках и даже в кутэ? Это же никому не нужно, раз никто пропозалы не писал.

  3. Почему рефлексия всё же появится в C++26? Она же не нужна. Совсем не нужна.

  4. Раз вы уж посмотрели про исключения, то почему в стандартных потоках их нет, а надо проверять дурацкие флаги? Наверное, тут что-то с надёжностью )

  5. Зачем нам концепты, когда есть замечательный SFINAE (привет всем языкам с дженериками сразу)?

По многопоточность и корутины даже вспоминать страшно. Равно как про мертворождённые модули. Такой же сборщик мусора уже похоронили.

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

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

Надо же, какой интересный мальчик, комментарии прошерстил даже.

Это и не требовалось :) Я наблюдал ваше избиение в прямом эфире. В остальном ваш комментарий - все то же самое балабольство. Вот Зверович взял и написал референсную имплементацию std::format, сначала в виде отдельной библиотеки, потом написал пропозал, и вот он в стандарте. Видите, никто не мешал, просто человек взял и сделал, а до него только такие как вы ныли в интернетиках, что им что-то опять недодали.

Раз вы уж посмотрели про исключения, то почему в стандартных потоках их нет, а надо проверять дурацкие флаги? Наверное, тут что-то с надёжностью )

Это у вас что-то с матчастью, как обычно. Все ваши остальные фантазии о том, что там кому нужно или не нужно, как-то комментировать бессмысленно.

Так напишите пропозал :) Условный трим ехал в стандарт 100500 лет не потому, что он никому не нужен, а потому что все всё равно сидят с бустами, абсейлями и т. д. ради их других фич, и им проще написать boost::trim чем писать бумажки чтобы в будущем можно было писать std::trim

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

constexpr - это ещё и обещание разработчика кода, что код внутри себя не читает ввод/вывод, настройки и т.п., что не возможно во время компиляции.

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

const int A[] = {
 #include "data.txt"
};

и оно вполне будет работать.

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

Однако да, возможность читать файлы во время компиляции была бы неплоха. В том же Rust она есть и её пользуются.

В C++26 есть все шансы увидеть #embed https://wg21.link/P1967

Так что встраивание ресурсов можно будет делать стандартным способом

В C++26 есть все шансы увидеть #embed

Не прошло и полвека:) Хотя нет, прошло, год появления языка Си 1969, а это настолько низкоуровневая и фундаментальная фича, что она по здравому смыслу должна была быть еще в первой версии Си. Ну также как и бинарные литералы 0b00011011

Так что встраивание ресурсов можно будет делать стандартным способом

Встраивание ресурсов это простейший юзкейс. Истину говорю - как только constexpr чтение файлов появится, так сразу захотят сделать кодогенерацию из внешних конфигов (xml, json...) средствами компилятора. К примеру, перенести на уровень метапрограммирования метакомпилятор Qt и генерировать из ui файлов код на c++ без всяких внешних утилит.

Не прошло и полвека:) Хотя нет, прошло, год появления языка Си 1969, а это настолько низкоуровневая и фундаментальная фича, что она по здравому смыслу должна была быть еще в первой версии Си.

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

так сразу захотят сделать кодогенерацию из внешних конфигов (xml, json...) средствами компилятора

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

А лично я думаю, что...

byte действительно стоило зафиксировать на 8 битах. А для остального ввести терминологию WORD/DWORD и QWORD и их делать разного размера и занести их как ключевые типы.

Какой вообще смысл в DWORD и QWORD переменного размера? Настоящее двойное слово - уже крайне неудобный в обращении тип (ибо занимает два регистра), а уж QWORD...

Единственная причина существования QWORD в некоторых библиотеках - в том, что WORD исторически привязали к 16 битам и для более широких типов потребовались другие имена. Однако, если "отпустить" WORD и позволить ему вырасти до настоящего размера машинного слова - то DWORD и QWORD потеряют свой смысл.

в том, что WORD исторически привязали к 16 битам

При этом, скажем, на ARM терминология другая - там word 32 бита, а 16 - это half word (и на 32, и на 64битных процессорах). Так что в отвязанный от архитектуры стандарт тащить эту путаницу вдвойне бессмысленно )

Стоило ввести типы произвольной битовой длины signed<N> и unsigned<N>. В LLVM такие типы даже есть готовые, правда когда я последний раз смотрел - при попытке их применения всё падало, но с тех пор уже много времени и версий прошло, может и исправили.

zig в свое время даже адаптировал целочисленные типы произвольного размера, теперь у него как и у LLVM можно явно просить какие-нибудь i3, u7, i17, u65539 . Знал математиков, которые делали модификации компилятора для поддержки безразмерных int , но вроде для C, чтобы работать с бинарными машинами тьюринга.

Вообще, как будто интересно… Я глянул предложение о рефлексии и мне интересно немного: почему функции std::meta решили сделать отдельными функциями, а не методами std::meta::info? Хотя, учитывая, что в плюсах у базовых типов впринципе иное поведение, чем у производных, а данный как будто сделали таковым, чтобы разные компиляторы могли считать его таковым… Иначе, не знаю как-то, странное дизайнерское решение, мне кажется.

Насчёт байта: пусть эксперименты с размером это и хорошо, но слова "байт = 8 бит" так у всех в голове укоренились, что мало кто бы стал писать исходя из обратного. Хотя, возможно, я не знаю какие-то нишевые области? Ну, многие языки уже приняли данное высказывание как стандарт, в любом случае. Отчасти к сожалению, ибо стандартизация чего-то это как отрезать ноги (взамен на то, чтобы вас не догнали с плохими намерениями)) ).

Про контракты: такое как будто лучше работает только если изначально является фишкой языка. Иначе получится как с добавлением добавлением nullability в C#. Или штук вроде @NotNull в java — такой пример точнее. Вроде и окей, но как по мне не на уровне всего языка с принуждением вручную ставить if и отслеживанием локально допустимых значений такое будет так себе, лишь пускаться исключениями или абортами. Но тут уж без отдельного диалекта никак, наверное, как в том странном предложении о safe-c++ (сейчас не найду его, но где-то на хабре есть). Кстати, такое вроде какие-то языки реализовывали как свои "кор-механики", можете напомнить, какие? red: немного погуглив, нашёл лишь spark. В остальном, эта идея какая-то малоиспользуемая, хах. В любом случае, оставить нечто в декларации это хорошо, но, довериться лишь на то, что программист, статический анализ (тут у него возможностей мало), тестирование и т.д. что-то там заметит — это не то, чтобы полноценно. Или же поясните, как это должно работать?

Насчёт байта: пусть эксперименты с размером это и хорошо, но слова "байт = 8 бит" так у всех в голове укоренились, что мало кто бы стал писать исходя из обратного. Хотя, возможно, я не знаю какие-то нишевые области?

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

Иначе получится как с добавлением добавлением nullability в C#.

Ну, я стал ловить куда меньше NRE в C# когда начал этой штукой пользоваться, так что фича вполне полезная.

Ну, я стал ловить куда меньше NRE в C# когда начал этой штукой пользоваться, так что фича вполне полезная.

Я немного перегнул, но всё же думаю, что тут чем раньше, тем лучше. Но тут как будто впринципе всё лучше заранее планировать.

Не понял, чем std::indirect отличается от std::unique_ptr ? Первый умеет в глубокое копирование?

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

Там еще довольно много отличий, вытекающих как раз из специфики типов-значений, вот что сходу вспомнилось:

  1. распространение константности на хранимый объект

  2. поддержка аллокаторов

  3. автоматическое оборачивание операторов сравнения (бинарных и operator<=>) и std::hash

  4. ну и да, автоматическое копирование и перемещение значения (в случае с std::polymorphic это еще и изящно решает проблему клонирования)

Есть референсная реализация, которую можно использовать уже сейчас

и что делает std::polymorphic? Запоминает указатель на то как нужно скопировать / ?мувнуть? значение?

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

ну чтож, ещё один класс по типу std::any std::function std::move_only_function std::copyable_function std::function_ref теперь ещё std::polymorphic, всё это должно решаться через один механизм, но они всё добавляют и добавляют...

По поводу количества бит в байте - тут мне кажется Hyrum's law действует. Столько кода написано с учётом размера байта 8 бит, что это уже де-факто стандарт. Весь этот код не сможет работать на экзотических системах с 16-битными "байтами", что даже не имеет смысл поддерживать в стандарте C++ такие системы. Кому же это нужно - пусть использует нестандартный компилятор C от производителя системы и пишет весь код с нуля с учётом нестандартного размера "байта".

Кстати, подобная стихийная стандартизация уже происходила ранее в C++. Размер int был изначально implementation-defined и был обычно 16 бит, позже (на 32-битных системах) стал 32 бит. Но с переходом на 64-битные системы видимо возникло столько проблем с поломкой существующего кода из-за другого размера int, что решили оставить его 32-битным. В Rust, видимо, чтобы не ходить по этим граблям, встроенные типы имеют фиксированный размер (за исключением usize).

Это, конечно, все интересно. Но когда же 26 стандарт будет в компиляторах, если еще 23 поддержан не полностью мажорной тройкой? Бешеный принтер может печатать. Комитеты комитетствовать, стандарты стандартизироваться, а практическое применение оставим на откуп комьюнити (кто контрибутит в gcc, llvm) и корпорациям (MS, Intel)?

На мой взгляд - темпы внедрения новых фичей просто бешеные. Посмотрите на другие языки со стандартом - некоторые стандарты 90х годов в них до сих пор не реализованы.

Ну сами изменения в C++ достаточно фундаментальные. Например C++20 модули затрагивают всю инфраструктуру C++ (компиляция, линковка, системы сборки, пакетные менеджеры, IDE, написание кода...). Одно это нововведение сравнимо по масштабам с Python2 -> Python3 (но при этом не ломает обратную совместимость).

и в итоге модули так и остались фичей, который ни один мало-мальски серьезный проект не пользуется

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

Да, c количеством бит в байте — это они зря, зря...
С++ не такой простой язык. Он не для no-code-разработчика, типа менеджера по поставкам. В нём можно и НУЖНО было бы оставить такие нюансы!

Создайте компанию по производству процессоров с троичной арифметикой, займите заметную долю на рынке - тогда вас внимательно послушают.

Sign up to leave a comment.