При моделировании аппаратных регистров в C++11/14 я провожу некоторые эксперименты с constexpr и некоторыми другими вещами. У меня возникла проблема с тем, как заставить компилятор жаловаться там, где я хочу.

В моем классе ниже я определяю регистр как 32-битный и содержащий поля различной ширины с перечисляемыми значениями. Имя перечисления уникально для регистра, поэтому никогда не будет FIELD1_ON и FIELD2_ON. Я строю перечисление так, чтобы <32 бита маски поля> и <32 бита значения перечисления>, так что я могу управлять только установкой нужных битов и не менять неинтересные мне биты.

Я хочу иметь возможность выражать несколько полей, говоря ENUM1 | ENUM2 | ENUM3, но здесь у меня возникают проблемы.

Если ENUM1 и ENUM2 находятся в одном поле, я хочу, чтобы он выдавал ошибку компиляции, если это возможно, а затем ошибку времени выполнения, если нет.

Здесь мой код

constexpr uint32_t ffs ( uint32_t value )
{
    constexpr uint32_t DeBruijnSequence[32] =
    {
        0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
        31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
    };    
    return  DeBruijnSequence[ 
        (( ( value & ( -static_cast(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
            >> 27];
}

#define MAKE_ENUM_VAL(field,value) ((static_cast(field)<<32ULL) | (value<(field))))

class Register
{
public:

    enum struct Fields
        {
            FIELD1 =  0b00000000000000000000000000000001,
            FIELD2 =  0b00000000000000000000000000001110,
            FIELD3 =  0b00000000000000000000000011110000,
            FIELD4 =  0b00000000000000001111111100000000,
        };
    enum struct Enums : uint64_t 
    {
        ON    =  MAKE_ENUM_VAL(Register::Fields::FIELD1, 1),
        OFF   =  MAKE_ENUM_VAL(Register::Fields::FIELD1, 0),
        MODE0 =  MAKE_ENUM_VAL(Register::Fields::FIELD2, 1),
        MODE1 =  MAKE_ENUM_VAL(Register::Fields::FIELD2, 2),
        MODE2 =  MAKE_ENUM_VAL(Register::Fields::FIELD2, 3),
        MODE3 =  MAKE_ENUM_VAL(Register::Fields::FIELD2, 4),
        MODE4 =  MAKE_ENUM_VAL(Register::Fields::FIELD2, 5),
        MODE5 =  MAKE_ENUM_VAL(Register::Fields::FIELD2, 6),
    };
};

constexpr Register::Enums operator | (Register::Enums a, Register::Enums b)
{
    return 
        ((static_cast(a) & static_cast(b)) == 0) ? 
              static_cast(static_cast(a) | static_cast(b))
            : throw "Bad juju";
}

Вот мой тестовый код:

    Register::Enums MyEnum1 = Register::Enums::ON | Register::Enums::OFF; //desire failed compilation
    Register::Enums MyEnum2 = Register::Enums::ON | Register::Enums::MODE1; 
    Register::Enums MyEnum3 = Register::Enums::ON;
    Register::Enums MyEnum4 = Register::Enums::OFF;
    Register::Enums MyEnum;

    MyEnum = MyEnum3 | MyEnum4; //assert/throw at runtime

Я ХОЧУ, чтобы компиляция MyEnum1 завершилась неудачей. (Пытаюсь установить FIELD1 как в ON, так и в OFF). Я пробовал выполнить проверку в операторе | (&ing биты a и b должны иметь результат 0, что означает отсутствие перекрытия), но лучшее, что я могу сделать, это перейти к версии выполнения, а затем выполнить assert.

.

Я пробовал возиться со статическим assert(), но похоже, что constexpr не нравится, когда он внутри него. Я пробовал и другие варианты (пытался определить, когда я нахожусь в constexpr, используя некоторые странные конструкции, которые я нашел здесь, на stackoverflow), но, похоже, не могу найти ничего, что дало бы мне желаемое поведение.

Есть предложения, которые не включают C++17/20? Мои встроенные компиляторы не настолько продвинуты.

Russ Schultz

Ответов: 1

Ответы (1)

Спецификатор constexpr объявляет, что можно оценить значение функции или переменной во время компиляции. Такие переменные и функции могут быть использованы там, где разрешены только константные выражения времени компиляции выражения (при условии, что соответствующие аргументы функции заданы). (cppref)

.

constexpr не гарантирует принудительную оценку во время компиляции, он позволяет параметрам быть значениями во время выполнения, поэтому static_assert здесь не работает.

Решением является использование параметра шаблона не типа, который требует, чтобы значение было константой.

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

Новая статическая функция-член Register:

template 
static constexpr Register::Enums flag_or()
{
    static_assert((static_cast(a) & static_cast(b)) == 0);
    return static_cast(static_cast(a) | static_cast(b));
}

и использование:

int main()
{
    Register::Enums MyEnum1 = Register::flag_or(); //desire failed compilation
    Register::Enums MyEnum2 = Register::flag_or(); 
    constexpr Register::Enums MyEnum3 = Register::Enums::ON;
    constexpr Register::Enums MyEnum4 = Register::Enums::OFF;
    Register::Enums MyEnum;

    MyEnum = Register::flag_or(); //assert/throw at runtime
}

Вывод:

Compiler returned: 1
Compiler stderr
: In instantiation of 'static constexpr Register::Enums Register::flag_or() [with Register::Enums a = Register::Enums::ON; Register::Enums b = Register::Enums::OFF]':
:50:91:   required from here
:43:77: error: static assertion failed
   43 |         static_assert((static_cast(a) & static_cast(b)) == 0);
      |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~

Ссылка на godbold.


В C++20 ключевое слово consteval, вероятно, можно было бы применить к операторной функции "|" (or), но, к сожалению, в вашем случае это не работает.

2022 WebDevInsider