LINUX.ORG.RU

То что сложно реализовать на других языках

 


0

3

Разбираюсь с библиотекой qsp и решил с вами поделится кусочком кода, а точнее реализация crc32. Автор кода, решил создать свой собственный crc32, на базе CRC-32/ISO-HDLC, взяв от туда таблицу, но изменил стартовое значение, и еще по мелочи:

int qspCRC(void *data, int len)
{
    unsigned char *ptr;
    int crc = 0;
    ptr = (unsigned char *)data;
    while (len--)
        crc = (qspCRCTable[(crc & 0xFF) ^ *ptr++] ^ crc >> 8) ^ 0xD202EF8D;
    return crc;
}

В чем прелесть: Использует int, но фактически работает как unsigned int. Вся логика на переполнение - и это как то работает, хотя вроде это UB. Автор кода крут (без шуток), я не спорю, но переписать это на любой другой язык, который будет контролировать переполнение - будет крайне сложно. Почему автор не использовал любой из стандартных алгоритмов crc, как он использовал библиотеку для regexp - загадка.

★★★★
Ответ на: комментарий от alysnix

это детали. у них там еще чота наворочено. надо посмотреть чем параметр - просто трейт, отличается от «динамический трейт».

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

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA
PrintableInt{10}.print();
PrintableFloat{1.1}.print();
test1(PrintableInt{10});
test1(PrintableFloat{10.0});

ну такой синтаксис, чтобы числа принтовать - это перегиб.

Ты просто сделал обертки, с общим методом print. Чтобы его вызвать нужно создать временный обьект c vmt. И хотя ты в нектором смысле сэмулировал растовый трейт-обьект(что тоже временный обьект c vmt) - но тебе пришлось это делать явно и руками. и ты не добился простой формы вызова, навроде

10.print()

или

int x = 100;
x.print()
alysnix ★★★
()
Ответ на: комментарий от alysnix

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

Я не цппист, явно же есть возможность сделать автоконверт типа в другой. Хотя с явным указанием даже преимущество есть, можно делать несколько impl для одного struct. Мне интереснее семантика, про синтаксис к волшебникам С++.

Ты просто сделал обертки, с общим методом print. Чтобы его вызвать нужно создать временный обьект c vmt.

Нет нет нет, как и в Rust, нужно будет задействовать +1 параметр что бы положить в него таблицу о которой ты говорил (vtable). То есть вся операция заключается в добавлении 1 теневого параметра.

IntPrintable это ссылка на объект которую создавать и не надо, объект у нас и так есть, и vtable который уже создан, осталось положить к вызову виртуальной функции ссылку на нее.

IntPrintable, FloatPrintable как раз образуют пару (T, vtable).

Я вот выше как раз код на ассемблере кидал, чистого неизвестного виртуального вызова:

mov     QWORD PTR [rsp], OFFSET FLAT:"vtable for PrintableInt"+16
mov     DWORD PTR [rsp+8], 10
call    "test1(Printable const&)"
Сначала ложим vtable, потом ложим само число. Это же твоя пара как ты и хотел.

Вот пример с std::string, тоже всего 1 доп.параметр: https://godbolt.org/z/ToWbn3cjx

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 7)
Ответ на: комментарий от MOPKOBKA

Нет нет нет, как и в Rust, нужно будет задействовать 1 регистр что бы положить в него таблицу о которой ты говорил (vtable). То есть вся операция заключается в добавлении 1 теневого параметра.

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

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

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

Но выигрыш в битве, это не выигрыш в войне.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Хотя я сейчас понял что в Rust есть преимущество для вызова dyn Trait, он всегда передает его как структуру с двумя полями, и распаковывает по регистрам, а С++ не подозревает что у нас всего два поля по сути, и создает объект на стеке (так что создавать на стеке придется два поля все же), и уже дает ссылку на стек.

Так что в C++ вызовы Trait не такие эффективные на Linux ABI. Интересно как у Rust дела на MS ABI.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от alysnix

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

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

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

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

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

до магазина я так и не дошел, потому откланиваюсь на полчасика.

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

В Rust принимающая функция всегда знает что ей передается пара.

Но это получается что у Rust нету dynamic_cast, RTTI. Можно сказать что C++ более рантаймовый язычек (конкретно в этом плане), и виртуальные вызовы немного эффективнее в Rust не нужно еще лишнее поле в классах, RTTI все равно все отключают, царь если что опровергнет.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 6)
Ответ на: комментарий от MOPKOBKA

Кто то реализует для некого типа трейт с функцией как у тебя, и тебе придется менять название своей функции!

менглированные имена то внутри:

типа так - print_of_trait_printable_for_int

чтобы попуталось, надо чтобы совпало все.

alysnix ★★★
()
Ответ на: комментарий от anonymous

Бред. Не может в строгом статически типизированном языке «потеряться» тип (без пунинг каста), это гарантированная ошибка компиляции. И в расте нет подтипирования(кроме лайфтаймов). А статический диспатч и вывод типов есть и по входу и по возврату:

pub fn add<S>(a: impl Into<S>, b: impl Into<S>) -> S
    where S: Add<Output=S>
{
    a.into() + b.into()
}

https://godbolt.org/z/qo5PzK1jq

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

zurg
()
Ответ на: комментарий от zurg

Бред. Не может в строгом статически типизированном языке «потеряться» тип.

Если он не потерялся, как получить к нему доступ из функции? Ну хотя бы static_assert(sizeof(T) == sizeof(int))?

MOPKOBKA ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Указываешь что тип должен иметь свойство - иметь известный в компилтайме размер - т.е. Sized, всё: https://godbolt.org/z/s9Yzjf8sc

zurg
()
Последнее исправление: zurg (всего исправлений: 1)
Ответ на: комментарий от zurg

Хорошая заготовка для примера, ты правильно понял, только тут опять вызов метода абстрактного класса в ВВ, хотя информация доступна в КТ о размере. У Rust нету static_assert из С, что бы проверить статически размер?

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

нет тут никаких абстрактных классов,

Rust нету static_assert

есть, это обычный assert в конст контексте: https://godbolt.org/z/oM8a83zPn но здесь можно и без конст, компилятор всё равно выкинет т.к. предикат вычисляем в компилтайме

zurg
()
Последнее исправление: zurg (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Фича текущей реализации, именнованный конст глобален по всем генерик экземплярам функции, соответсвенно не может иметь возможные разные значения. Есть rfc и фича в экспериментальном статусе которая «исправляет» такое поведение. Но это не имеет отношения к выводу типов. Вот это имеет: https://godbolt.org/z/ac6hhjWG4 - по другую сторону орфанного правила.

pub fn add<B, Out, A: AddOp<B,Out>>(a:A, b:B )-> Out {
    a.add_op(b)
}

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

zurg
()
Последнее исправление: zurg (всего исправлений: 2)
Ответ на: комментарий от zurg

именнованный конст глобален по всем генерик экземплярам функции
Но это не имеет отношения к выводу типов.

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

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

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

Тут важно учесть, что оборачивание объекта в структуру, пусть даже с методами, это zero cost абстракция. До момента пока возможна девиртуализация.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Пример легко повторяется на классах-трейтах,

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

pub fn add<B, Out, A: AddOp<B,Out>>(a:A, b:B )-> Out {
    a.add_op(b)
}

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

alysnix ★★★
()

Дык у тебя же crc в цикле меняется ?

Да и шляпы с int/unsigned ?

Ну это стандартное дело в си, фкнкция доверяет пришедшему len..

ptr растёт, ну len раз. Особого криминала не вижу, в библиотечных методах такого полно - memset, memcpy, strcpy и тд.

Так что код более-менее читаем.

tr0_
()
Ответ на: комментарий от alysnix

Тут надо применять и мои эмулируемые трейты и классы одновременно. Только на классах такое не написать, но классы идеально будут эмулировать работу именно трейтов. Я про то что добавив в трейт темплейт параметр, легко выразить AddOp<In, Out>.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)