Могу ли я переинтерпретировать параметр функции constexpr?

#c #compiler-errors #bit-manipulation #c 17 #constexpr

#c #ошибки компилятора #манипулирование битами #c 17 #constexpr

Вопрос:

Я хочу написать функцию, оцениваемую во время компиляции, она принимает указатель на массив из 4 байт и выводит int, который имеет тот же битовый шаблон, что и этот массив. Итак, я придумал:

 constexpr int f(const char* p) {
     return *reinterpret_cast<int*>(p);
}
  

Затем я хочу использовать f() так:

 switch(x) {
case f("GOOG"):
   // do something
case f("MSFT"):
   // do something
case f("NIKE"):
  // do something
}
  

Однако я получил ошибку компилятора:

ошибка: доступ к значению ‘"GOOG"’ через ‘int’ glvalue в случае константного выражения f("GOOG")
  1. Как исправить f() , чтобы он компилировался?
  2. Есть ли лучший способ достичь той же цели?

Комментарии:

1. Даже если он будет скомпилирован, это будет строгим нарушением псевдонимов и UB. Используйте битовые сдвиги, чтобы получить целое число из отдельных char s.

2. @HolyBlackCat Спасибо. кстати, что такое UB?

3. UB — это неопределенное поведение.

4. Спасибо. Я чувствую, что выполнение сдвига битов 4 раза не так элегантно, как просто рассматривать его как int . Нет ли способа сделать это безопасно?

5. Вы также можете создать int и memcpy массив в нем, но memcpy это не constexpr так.

Ответ №1:

Поздравляем, вы активировали карточку ловушки строгого сглаживания, и ваш код имеет неопределенное поведение (если он будет компилироваться).

В вашем коде мало ошибок, «правильная» версия:

  constexpr int f(const char* p) {
         return *reinterpret_cast<const int*>(p);
    }
  
  • reinterpret_cast невозможно отбросить const .
  • cursor->p опечатка?

Но поскольку const char* не указывает на an int , приведение к нему нарушает строгое правило псевдонимов. int не является одним из типов, которые могут использовать псевдонимы других — только std::byte, (unsigned) char can .

Самым чистым было бы это:

 #include <cstring>

constexpr int f(const char* p) {
         int val = 0;
         static_assert(sizeof(val)==4); // If the array is 4-byte long.
         std::memcpy(amp;val,p,sizeof val);
         return val;
    }

  

Но std::memcpy это не constexpr так, даже во время выполнения это, вероятно, не будет иметь никаких накладных расходов, компилятор может распознать это и переинтерпретировать байты самостоятельно.

Так что переходите к битовому сдвигу:

 constexpr int f(const char* p) {
       int value=0;
       using T = decltype (value);
       for(std::size_t i =0; i< sizeof(T);  i)
        value|= (T)p[i] << (8*i);

    return value;
    }

int main(){

    // @ == 64
    // 1077952576 = 01000000 01000000 01000000 01000000
    static_assert(f("@@@@") ==1077952576);
}
  

Просто чтобы быть педантичным "@@@@" , имеет длину 5, а не 4.

Комментарии:

1. курсор был опечатан. исправлено. TY.

2. Можете ли вы объяснить, что это value|= (T)*p << (8*i); делает? Похоже, что он использует только первый символ p, сдвигается влево на 1 байт и выполняет побитовое или со значением. т. е. использует ли он остальные символы? p[1], p[2] и т.д.

3. @ijklr Ой, извините, конечно, в нем отсутствует индекс, глупо, что я сделал слишком простой тестовый пример. Исправлено.

4. Спасибо. и причина, по которой вы использовали (T) вместо reinterpret_cast<T> , заключается в том, что внутри constexpr функций вы не можете переинтерпретировать, верно?

5. Жаль, что я не могу просто преобразовать его в int и не нужно выполнять цикл:(