#prog #cpp
В стандартной библиотеке C++ есть unordered контейнеры, которые для проверки принадлежности элементов контейнеру используют хэш-функции (помимо равенства, разумеется). Для того, чтобы хэшировать значение, нужно знать, как это делается.
В C++ операции хеширования можно переопределять для конкретных контейнеров (это один из шаблонных параметров), но по умолчанию используется std::hash. Для того, чтобы определить операцию хеширования для своего типа, нужно написать специализацию
Пусть у нас вот такой простой тип:
Попробуем сделать его хешируемым:
И вот тут мы сталкиваемся с проблемой: при условии, что у нас все поля хэшируемые, как нам получить хэш от всех них? Увы, std тут вообще никак не помогает. Максимум, что могут предложить на просторах интернета — это использовать boost::hash_combine (это даже рекомендуют на cppreference.com). Мало того, что тащить буст ради этого не хочется, так ещё и комбинирование происходит на уровне готовых хэшей. Это фактически приводит к двойному хэшированию, что обычно на качестве хэш-функции сказывается не в лучшую сторону.
В теории можно было бы организовать разделение на саму хэш-функцию и на описание того, какие и в каком порядке поля типа хэшируются... То есть сделать так, как в Rust. И это даже предлагали сделать для C++ в предложении N3980 aka Types Don't Know #. И подано это предложение было... 24 мая 2014 года. То есть десять лет назад, да. А воз и ныне там.
В стандартной библиотеке C++ есть unordered контейнеры, которые для проверки принадлежности элементов контейнеру используют хэш-функции (помимо равенства, разумеется). Для того, чтобы хэшировать значение, нужно знать, как это делается.
В C++ операции хеширования можно переопределять для конкретных контейнеров (это один из шаблонных параметров), но по умолчанию используется std::hash. Для того, чтобы определить операцию хеширования для своего типа, нужно написать специализацию
std::hash для своего типа (обязательно в пространстве имён std) и написать свою реализацию operator(), которая будет принимать хэшируемый объект и возвращать std::size_t в качестве результата.Пусть у нас вот такой простой тип:
struct Point {
int x;
int y;
};Попробуем сделать его хешируемым:
#include <cstddef>
#include <functional>
template <> struct std::hash<Point> {
std::size_t operator()(const Point& p) {
// а как...
}
};
И вот тут мы сталкиваемся с проблемой: при условии, что у нас все поля хэшируемые, как нам получить хэш от всех них? Увы, std тут вообще никак не помогает. Максимум, что могут предложить на просторах интернета — это использовать boost::hash_combine (это даже рекомендуют на cppreference.com). Мало того, что тащить буст ради этого не хочется, так ещё и комбинирование происходит на уровне готовых хэшей. Это фактически приводит к двойному хэшированию, что обычно на качестве хэш-функции сказывается не в лучшую сторону.
В теории можно было бы организовать разделение на саму хэш-функцию и на описание того, какие и в каком порядке поля типа хэшируются... То есть сделать так, как в Rust. И это даже предлагали сделать для C++ в предложении N3980 aka Types Don't Know #. И подано это предложение было... 24 мая 2014 года. То есть десять лет назад, да. А воз и ныне там.
Arthur O’Dwyer
Why can’t I specialize std::hash inside my own namespace?
This question comes up a lot on the cpplang Slack.
Suppose I have a class named my::Book, and I want to put it into a std::unordered_set.
Then I need to write a std::hash specialization for it. So I write:
Suppose I have a class named my::Book, and I want to put it into a std::unordered_set.
Then I need to write a std::hash specialization for it. So I write:
🤡9🤷6🤔3🤣3
#prog #cpp
doctest is a new C++ testing framework but is by far the fastest both in compile times (by orders of magnitude) and runtime compared to other feature-rich alternatives. It brings the ability of compiled languages such as D / Rust / Nim to have tests written directly in the production code thanks to a fast, transparent and flexible test runner with a clean interface.
Советую также посмотреть, чем отличается от прочих фреймворков для тестирования в C++.
doctest is a new C++ testing framework but is by far the fastest both in compile times (by orders of magnitude) and runtime compared to other feature-rich alternatives. It brings the ability of compiled languages such as D / Rust / Nim to have tests written directly in the production code thanks to a fast, transparent and flexible test runner with a clean interface.
Советую также посмотреть, чем отличается от прочих фреймворков для тестирования в C++.
🔥5🤔2👎1🤣1
TIL что для #cpp есть предложение закрепить в стандарте тот факт, что в байте ровно 8 бит. Сейчас это не так: стандарт (и сишный тоже) требует, чтобы в char было минимум 8 бит, но точное их количество может быть больше, и это количество записано макросом
CHAR_BIT из limits.h/climits.👍6🤡5🫡4😁1
#prog #rust #cpp #article
Type Inference in Rust and C++
<...>My feeling is that literally everything above is indicative of a trade-off pattern.
If you want to have a fancy, bespoke modern type checker with Hindley-Milner type inference semantics, you need to accept one of the following:
1. Bad performance for your type checker with a risk of exponential blow-up.
2. No features that look anything like “the compiler picks the best option out of several ones”. No function overloading, implicit conversions, etc.
Надо отдельно отметить, что deref coercion под "pick the best option out of several ones" не подпадает —
Type Inference in Rust and C++
<...>My feeling is that literally everything above is indicative of a trade-off pattern.
If you want to have a fancy, bespoke modern type checker with Hindley-Milner type inference semantics, you need to accept one of the following:
1. Bad performance for your type checker with a risk of exponential blow-up.
2. No features that look anything like “the compiler picks the best option out of several ones”. No function overloading, implicit conversions, etc.
Надо отдельно отметить, что deref coercion под "pick the best option out of several ones" не подпадает —
Target является у трейта ассоциированным типом, а не параметром, поэтому реализаций Deref у каждого конкретно взятого типа не более одной🤔3
#prog #cpp #article
How C++ Resolves a Function Call
Взгляд на порядок разрешения имён при вызове функции в C++ с высоты птичьего полёта, с примером, который проходит по всем шагам.
How C++ Resolves a Function Call
Взгляд на порядок разрешения имён при вызове функции в C++ с высоты птичьего полёта, с примером, который проходит по всем шагам.
👍12🤮3
#prog #cpp #article
Carbon is not a programming language (sort of)
TL;DR: Carbon не столько про сам язык, сколько про процесс эволюции языка.
Carbon is not a programming language (sort of)
TL;DR: Carbon не столько про сам язык, сколько про процесс эволюции языка.
MOND←TECH MAGAZINE
Carbon is not a programming language (sort of)
Within C++, there is a much smaller and cleaner language struggling to get out.
👍7
#prog #cpp #article
Why safety profiles failed
TL;DR:
10 лет назад Страуструп и Ко представили идею safety profiles: набор стандартизированных статических анализаторов, которые бы увеличивали безопасность кода на C++, причём практически без изменений исходного кода, и которые можно было бы активировать одной командой компилятора. Идея оказалась настолько привлекательной, что комитет по C++ (WG21) принял несколько предложений касательно профилей.
Однако за 10 лет весь выхлоп от профилей весьма мал: криво работающий -Wlifetime и... Вроде бы всё. Даже спецификации какой-то за столько времени так и не сделали.
В своём тексте Sean Baxter, автор компилятора Circle, пишет о том, почему идея safety profiles не работает и, более того, в принципе не может работать.
Why safety profiles failed
TL;DR:
10 лет назад Страуструп и Ко представили идею safety profiles: набор стандартизированных статических анализаторов, которые бы увеличивали безопасность кода на C++, причём практически без изменений исходного кода, и которые можно было бы активировать одной командой компилятора. Идея оказалась настолько привлекательной, что комитет по C++ (WG21) принял несколько предложений касательно профилей.
Однако за 10 лет весь выхлоп от профилей весьма мал: криво работающий -Wlifetime и... Вроде бы всё. Даже спецификации какой-то за столько времени так и не сделали.
В своём тексте Sean Baxter, автор компилятора Circle, пишет о том, почему идея safety profiles не работает и, более того, в принципе не может работать.
👍2
#prog #cpp #article
C++26: no more UB in lexing
(на практике, правда, главные реализации и так нормально такой код обрабатывали)
C++26: no more UB in lexing
(на практике, правда, главные реализации и так нормально такой код обрабатывали)
Sandor Dargo’s Blog
C++26: no more UB in lexing
If you ever used C++, for sure you had to face undefined behaviour. Even though it gives extra freedom for implementers, it’s dreaded by developers as it may cause havoc in your systems and it’s better to avoid it if possible. Surprisingly, even the lexing…
🤣9😁2🤯1
#cpp
P2723R1 Zero-initialize objects of automatic storage duration
Можно прочитать только ради секции об отзывах
P2723R1 Zero-initialize objects of automatic storage duration
Можно прочитать только ради секции об отзывах
😁7🤔3👍1
#prog #cpp #rust #article
Why we didn't rewrite our feed handler in Rust
Отдельно отмечается, что Rust в технологическом стеке в этой компании уже есть и успешно используется. Проблемы возникли с переписыванием конкретного компонента, который уже есть и написан на C++. Конкретно в тексте приведены три паттерна, которые валидны в C++ и не выразимы или выразимы неудобно на Rust.
Первое касается ограничений borrow checker-а. Вот какой пример приводят:
Простой и понятный код — но, к сожалению, выделяющий память в цикле. Логично было бы вынести аллокацию за цикл и очищать буфер в конце — но тогда компилятор не даёт скомпилировать код:
Второй паттерн — самоссылающиеся структуры, известная больная тема в Rust.
Третий паттерн — множество определений разных версий и унифицированный код для работы с ними (из-за необходимости поддержки разных версий схем обмена данных, насколько я понял). Пример из статьи на C++:
Унифицированный код для работы с обоими этими типами можно написать при помощи шаблонов:
Нетрудно видеть, как это обобщается на случай большего количества полей и различных версий. В Rust можно попробовать сделать нечто подобное, но это вырождается в бойлерплейт, облегчать который приходится макросами — иными словами, попытка повторить шаблоны из C++.
Why we didn't rewrite our feed handler in Rust
Отдельно отмечается, что Rust в технологическом стеке в этой компании уже есть и успешно используется. Проблемы возникли с переписыванием конкретного компонента, который уже есть и написан на C++. Конкретно в тексте приведены три паттерна, которые валидны в C++ и не выразимы или выразимы неудобно на Rust.
Первое касается ограничений borrow checker-а. Вот какой пример приводят:
fn process_source(sources: Vec<Source>) {
for source in sources {
let mut buffer: Vec<&[u8]> = Vec::new();
let data: Vec<u8> = source.fetch_data();
buffer.extend(data.split(splitter));
process_data(&buffer);
}
}Простой и понятный код — но, к сожалению, выделяющий память в цикле. Логично было бы вынести аллокацию за цикл и очищать буфер в конце — но тогда компилятор не даёт скомпилировать код:
error[E0597]: `data` does not live long enough
--> src/lib.rs:32:23
|
31 | let data: Vec<u8> = source.fetch_data();
| ---- binding `data` declared here
32 | buffer.extend(data.split(splitter));
| ------ ^^^^ borrowed value does not live long enough
| |
| borrow later used here
33 | process_data(&buffer);
34 | }
| - `data` dropped here while still borrowed
Второй паттерн — самоссылающиеся структуры, известная больная тема в Rust.
Третий паттерн — множество определений разных версий и унифицированный код для работы с ними (из-за необходимости поддержки разных версий схем обмена данных, насколько я понял). Пример из статьи на C++:
struct RecV1 {
uint32_t x;
uint32_t y;
}
struct RecV2 {
uint32_t x;
uint32_t y;
uint32_t z;
}Унифицированный код для работы с обоими этими типами можно написать при помощи шаблонов:
template <typename T>
T InitRec() {
T res;
res.x = 1;
res.y = 2;
if constexpr(std::is_same_v<T, RecV2>()) {
res.z = 3;
}
return res;
}
Нетрудно видеть, как это обобщается на случай большего количества полей и различных версий. В Rust можно попробовать сделать нечто подобное, но это вырождается в бойлерплейт, облегчать который приходится макросами — иными словами, попытка повторить шаблоны из C++.
👍10🤡5🔥1