Чисто сообщать об ошибке времени компиляции от constexpr без исключений? - PullRequest
0 голосов
/ 03 мая 2018

Я ищу способ вызвать ошибку времени компиляции из функции constexpr. Поскольку я нахожусь во встроенной системе, исключения C ++ должны оставаться отключенными (флаг GCC -fno-exceptions). Таким образом, способ сообщения об ошибках по умолчанию представляется невозможным.

Возможный способ, описанный в ошибка constexpr во время компиляции, но без накладных расходов во время выполнения , - это вызов функции не-constexpr, которая выдает ошибку, если реализация во время компиляции вынуждена. Однако это решение выдает довольно нечитаемые сообщения об ошибках, и реализация вынуждена возвращать возвращаемые значения мусора, чтобы заставить замолчать предупреждения «контроль может достигнуть конца не пустой функции».

Есть ли лучший способ, позволяющий предоставить пользовательское сообщение об ошибке?

Обратите внимание, что мне известно о static_assert и возможности преобразования функции в шаблон. Однако static_assert необходимо пересобрать довольно сложную логику блоков переключателей моего варианта использования, чтобы выдать ошибку, которая подвержена ошибкам и неуклюжа.

Пример использования:

constexpr SpiDmaTxStreams spiDmaTxStream(DmaId dmaId, DmaStreamId streamId) {
    switch (dmaId) {
        case DmaId::DMA_1:
            switch (streamId) {
                case DmaStreamId::Stream_4:
                    return SpiDmaTxStreams::Dma1Stream4;
                // ...
                default:
                    break;
            }
            break;
        case DmaId::DMA_2:
            switch (streamId) {
                case DmaStreamId::Stream_1:
                    return SpiDmaTxStreams::Dma2Stream1;
                // ...
                default:
                    break;
            }
            break;
    }
    // report compile-time error "invalid DMA-stream combination"
}

Ответы [ 3 ]

0 голосов
/ 03 мая 2018

Если вы можете добавить специальное значение ошибки в SpiDmaTxStreams enum ... скажем, SpiDmaTxStreams::ErrorValue ... Я предлагаю другое решение, опять же на основе шаблонной структуры, но с обращенной логикой: не специализированная структура и одиночная специализированная версия для static_error сообщений.

Я имею в виду ... если вы вернете SpiDmaTxStreams::ErrorValue в случае недопустимой комбинации

constexpr SpiDmaTxStreams spiDmaTxStream(DmaId dmaId, DmaStreamId streamId) {
    switch (dmaId) {
        case DmaId::DMA_1:
            switch (streamId) {
                case DmaStreamId::Stream_4:
                    return SpiDmaTxStreams::Dma1Stream4;
                // ...
                default:
                    return SpiDmaTxStreams::ErrorValue; // <<---- add this
                    break;
            }
        case DmaId::DMA_2:
            switch (streamId) {
                case DmaStreamId::Stream_1:
                    return SpiDmaTxStreams::Dma2Stream1;
                // ...
                default:
                    return SpiDmaTxStreams::ErrorValue; // <<---- add this
                    break;
            }
    }
    // report compile-time error "invalid DMA-stream combination"
}

Вы можете позвонить spiDmaTxStream(), чтобы присвоить значение значению шаблона (осторожно: код не проверен) следующим образом

template <DmaId I1, DmaStreamId I2,
          SpiDmaTxStreams IR = spiDmaTxStream(I1, I2)>
struct foo
 { static constexpr auto value = IR; };


template <DmaId I1, DmaStreamId I2>
struct foo<I1, I2, SpiDmaTxStreams::ErrorValue>
 {
   // where DmaId::DMA_1/DmaStreamId::Stream_4 is an 
   // acceptable combination
   static_assert( (I1 == DmaId::DMA_1) && (I2 == DmaStreamId::Stream_4),
                  "your error message here" );
 };

и опять вместо

constexpr value id = spiDmaTxStream(DmaId::DMA_2, DmaStreamId::Stream_1);

Вы можете написать

constexpr value id = foo<DmaId::DMA_2, DmaStreamId::Stream_1>::value;

Если dmaId / streamId неприемлемо, spiDmaTxStream() возвращает SpiDmaTxStreams::ErrorValue, поэтому активируется специализированная версия foo и отправляется сообщение static_error().

0 голосов
/ 03 мая 2018

Один из способов вызвать ошибку компиляции constexpr - вызвать UB. Самый простой способ активировать UB - через __builtin_unreachable(). К сожалению, это не учитывает сообщение, но мы можем обернуть его в макрос.

В качестве примера этой программы:

#define CONSTEXPR_FAIL(...) __builtin_unreachable()

constexpr int foo(int a, int b) {
    switch (a) {
    case 0:
        return b;
    case 1:
        if (b == 2) return 3;
        break;
    }

    CONSTEXPR_FAIL("Mismatch between a and b");
}

int main() {
    static_assert(foo(0, 2) == 2, "!");

    // constexpr int i = foo(2, 2);
}

Прекрасно компилируется на gcc 7.2 и clang 5.0 с c ++ 14. Если вы откомментируете вызов на foo(2,2), gcc выдает:

<source>: In function 'int main()':
<source>:18:26:   in constexpr expansion of 'foo(2, 2)'
<source>:1:50: error: '__builtin_unreachable()' is not a constant expression
 #define CONSTEXPR_FAIL(...) __builtin_unreachable()
                             ~~~~~~~~~~~~~~~~~~~~~^~
<source>:12:5: note: in expansion of macro 'CONSTEXPR_FAIL'
     CONSTEXPR_FAIL("Mismatch between a and b");
     ^~~~~~~~~~~~~~

и лязг излучает:

<source>:18:19: error: constexpr variable 'i' must be initialized by a constant expression
    constexpr int i = foo(2, 2);
                  ^   ~~~~~~~~~
<source>:12:5: note: subexpression not valid in a constant expression
    CONSTEXPR_FAIL("Mismatch between a and b");
    ^
<source>:1:29: note: expanded from macro 'CONSTEXPR_FAIL'
#define CONSTEXPR_FAIL(...) __builtin_unreachable()
                            ^
<source>:18:23: note: in call to 'foo(2, 2)'
    constexpr int i = foo(2, 2);
                      ^

Это работает для вас? Это не совсем static_assert, поскольку компилятор не отправляет вам сообщение напрямую, но он заставляет компилятор указывать на правильную строку, и сообщение будет находиться в стеке вызовов.

0 голосов
/ 03 мая 2018

Извините, потому что вы задали совершенно другое решение, но если

dmaId и streamId - литералы или constexpr (члены класса enum), и ожидается, что вся функция будет работать только во время компиляции

для передачи dmaId и streamId в качестве не шаблонного параметра мне кажется неправильным.

Мне кажется, что-то гораздо проще: извините, код не проверен

// generic foo: to force a comprehensible error message 
template <DmaId I1, DmaStreamId I2>
struct foo
 {
   static_assert( (I1 ==  DmaId::DMA_1) && (I2 == DmaStreamId::Stream_4),
                  "your error message here" );
 };

// specialization with all acceptable combinations

template <>
struct foo<DmaId::DMA_1, DmaStreamId::Stream_4>
 { static constexpr auto value = SpiDmaTxStreams::Dma1Stream4; };

// ...

template <>
struct foo<DmaId::DMA_2, DmaStreamId::Stream_1>
 { static constexpr auto value = SpiDmaTxStreams::Dma2Stream1; };

// ...

Итак, вместо

constexpr value id = spiDmaTxStream(DmaId::DMA_2, DmaStreamId::Stream_1);

Вы можете написать

constexpr value id = foo<DmaId::DMA_2, DmaStreamId::Stream_1>::value;
...