Есть ли способ получить доступ к произвольным данным известного размера в виде массива символов в контексте 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
должен выполнять прямое преобразование, и он должен выполнять такое преобразование с особым знанием того, каков каждый компонентный тип исходного и целевого объектов.