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

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

Я получаю довольно криптографическую c ошибку ...

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

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

Мой вопрос: есть ли любой способ интерпретации произвольных данных как простого старого массива байтов? Меня не волнует, насколько он уродлив или непереносим (если все это происходит во время компиляции). На данный момент достаточно просто решить случай с массивом wchar_t в качестве входных данных. Очевидно, я мог бы «просто» заново реализовать вычисления CR C для каждого типа, который я хочу обрабатывать отдельно, но я бы предпочел не делать этого, если это вообще возможно (и действительно, это было бы довольно сложно для чего-то более сложного, чем массив 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 Ответ

3 голосов
/ 01 августа 2020

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

...