Какой тип лучше выбрать для функции, которая принимает блок памяти? Автор начинает с классического варианта из C: void DoSomething(const void* p, size_t numBytes). Просто, понятно: передаёшь указатель на данные и размер в байтах.
Некоторые C++-программисты воротят нос от void*» и предлагают заменить его наconst uint8_t*» — мол, это «безопаснее». Но тогда вызов DoSomething(&data, sizeof(data)) для пользовательской структуры MyCustomData перестаёт компилироваться. Приходится вставлять reinterpret_cast<const uint8_t*>(&data). Код сразу усложняется и уродуется. Зачем?
Другие советуют std::span. Мол, это «современно» и «C++20». Автор иронизирует: вот только сигнатура превращается в шаблон: template <typename T> void DoSomething(std::span<T> data) или ещё более запутанные варианты. Сложность зашкаливает. Даже std::span<const uint8_t> всё равно сложнее void*. При этом никакой реальной выгоды — только больше шаблонного шума и нечитаемый код.
Автор считает, что некоторые приверженцы «современного C++» потеряли вкус к хорошему читаемому коду. Они специально выбирают сложные и некрасивые решения, хотя язык уже дал идеальную абстракцию для блока памяти — void*. Её можно смело использовать и в C++.
Как приятное дополнение — SAL-аннотации. Например: void DoSomething(_In_reads_bytes_(numBytes) const void* p, _In_ size_t numBytes). Аннотация _In_reads_bytes_ явно говорит анализаторам кода, что указатель p указывает на входную память для чтения, а её размер в байтах задаётся параметром numBytes. Вызов остаётся чистым: DoSomething(&data, sizeof(data)), при этом анализаторы могут ловить ошибки работы с памятью.
Итог: не надо усложнять код ради мнимой «безопасности». void* + размер — это ясно, работает и не требует лишних приведений. А если хочется подсказок для статического анализа — используй SAL.