Есть ли способ получить доступ к произвольным данным известного размера в виде массива символов в контексте constexpr/consteval?

Я пытаюсь реализовать что-то, что будет принимать произвольные биты данных (которые известны во время компиляции) и вычислять их CRC как consteval, поэтому я могу использовать его, например, для индексации таких данных с помощью целочисленных ключей без дополнительных затрат времени выполнения. У меня он работает, когда ввод представляет собой строковый литерал char, но я изо всех сил пытаюсь заставить его работать, когда ввод являетсяwchar_t строковый литерал.

Я получаю довольно загадочную ошибку...

error: accessing value of '"T\000e\000s\000t\000\000"' through a 'const char' glvalue in a constant expression

... что, похоже, вызвано использованием reinterpret_cast в контексте constexpr (что, по-видимому, недопустимо)

Мой вопрос: есть ли способ интерпретировать произвольные данные как простой старый массив байтов? Меня не волнует, насколько он уродлив или непереносим (если все это происходит во время компиляции). А пока просто решаем случай с массивомwchar_tв качестве ввода будет достаточно. Очевидно, я мог бы "просто" заново реализовать вычисления CRC для каждого типа, который я хочу обрабатывать отдельно, но я бы предпочел не делать этого, если это вообще возможно (и действительно, это было бы довольно сложно для чего-то более сложного, чем массив POD)

Для справки, код ошибки выглядит следующим образом:

// Details of CRCInternal omitted for brevity
template <size_t len> consteval uint32_t CRC32(const char (&str)[len])
{
    return CRCInternal::crc32<len - 1>(str) ^ 0xFFFFFFFFu;
}

template <size_t len> consteval uint32_t CRC32FromWide(const wchar_t (&filename)[len])
{
    return CRC32(reinterpret_cast<const char(&)[len * sizeof(wchar_t)]>(filename));
}

void main()
{
    CRC32FromWide(L"Test"); // <==== Error
}

1 ответ

Решение

Объектная модель C++ обычно является фикцией, соглашением между программистом, пишущим код, и компилятором, генерирующим двоичный исполняемый файл. Для исполняемого файла объекты не существуют; это просто биты, хранящиеся в памяти. Таким образом, вы можете воспользоваться тем фактом, что в C++ есть десятки лазеек, которые можно использовать, чтобы эффективно притвориться, что объектная модель нереальна. Многие из них демонстрируют неопределенное поведение, но ни один компилятор не собирается проверять эти нарушения объектной модели и останавливать вас. Вы нарушили свой конец контракта, но компилятор не обратил на это внимания, так что вам это сойдет с рук.

Это не так при вычислении постоянного выражения. Скомпилированный исполняемый файл запускается на CPU; оценка константного выражения выполняется в компиляторе. Объектная модель не должна отображать "биты", "память" или что-то подобное; это может быть реальная объектная модель с полным отслеживанием и анализом срока службы.

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

Во время компиляции объекты не хранятся в байтах. Так что нельзя относиться к ним так, как если бы они были.

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

Это плохо.

C++ 20-х годов std::bit_castсуществует и может помочь, но даже это не все. Тип подходит только дляconstexpr bit_cast-ing, если он TriviallyCopyable и не хранит указатели (среди прочего). Это потому, что указатели времени компиляции - это не просто адреса; это сложный тип данных, который должен помнить, на какой объект он указывает (в противном случае было бы невозможно обнаружить, когда выstatic_cast их к какому-то несвязанному типу и пытаются получить доступ к объекту через неправильный тип).

Но если вы ограничите свои типы теми, которые constexpr bit_castв состоянии, тогда вы можете bit_cast их в массив их размера.

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

Другие вопросы по тегам