简体   繁体   中英

Cleanly report compile-time error from constexpr without exceptions?

I am looking for a way to raise a compile-time error from a constexpr function. Since I am on an embedded system, C++ exceptions need to remain disabled (GCC flag -fno-exceptions). Thus, the default way of error reporting seems to be infeasible.

A possible way described in constexpr error at compile-time, but no overhead at run-time is to call a non-constexpr function, which throws an error if compile-time implementation is forced. However, this solution gives rather unreadable error messages and the implementation is forced to return garbage return values in order to silence "control may reach end of non-void function" warnings.

Is there a better way, which allows to provide a custom error message?

Please note, that I am aware of static_assert and the possibility to convert the function to a template. However, static_assert needs to reassemble the quite complex logic of the switch-blocks of my use-case in order to throw an error, which is error-prone and clumsy.

Example use-case:

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"
}

One way to trigger a constexpr compile error is to trigger UB. The simplest way to trigger UB is via __builtin_unreachable() . That unfortunately doesn't allow for a message, but we could wrap it in a macro.

As an example this program:

#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);
}

Compiles fine on gcc 7.2 and clang 5.0 with c++14. If you un-comment the call to foo(2,2) , gcc emits:

<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");
     ^~~~~~~~~~~~~~

and clang emits:

<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);
                      ^

Does this work for you? It's not quite a static_assert in that the compiler doesn't emit the message for you directly, but the it does get the compiler to point to the correct line and the message is going to be in the call stack.

Sorry, because you asked a completely different solution, but if

dmaId and streamId are literals or constexpr (enum class members) and the whole function is only expected to work at compile-time

to pass dmaId and streamId as not-template parameter seems to me the wrong way.

It seems to me a lot simpler something as follows (sorry: code not tested)

// 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; };

// ...

So, instead of

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

you can write

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

If you can add a special error value in SpiDmaTxStreams enum... say SpiDmaTxStreams::ErrorValue ... I propose another solution, again based on a template struct but with reverted logic: the not-specialized struct and a single specialized version for static_error messagge.

I mean... if you return SpiDmaTxStreams::ErrorValue in case of unacceptable combination

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"
}

You can call spiDmaTxStream() to give value to a template value (caution: code not tested) as follows

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" );
 };

and, again, instead of

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

you can write

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

If the dmaId / streamId is unacceptable, spiDmaTxStream() return SpiDmaTxStreams::ErrorValue , so the foo specialized version is activated and the static_error() message is in charge.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM