简体   繁体   English

毫无例外地从 constexpr 干净地报告编译时错误?

[英]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.我正在寻找一种从 constexpr 函数引发编译时错误的方法。 Since I am on an embedded system, C++ exceptions need to remain disabled (GCC flag -fno-exceptions).由于我使用的是嵌入式系统,因此 C++ 异常需要保持禁用状态(GCC 标志 -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. 在编译时 constexpr 错误中描述的一种可能方法,但在运行时没有开销是调用非 constexpr 函数,如果强制编译时实现,则会抛出错误。 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.请注意,我知道static_assert以及将函数转换为模板的可能性。 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.但是, static_assert需要重新组装我的用例的开关块的相当复杂的逻辑才能抛出错误,这很容易出错且笨拙。

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.触发 constexpr 编译错误的一种方法是触发 UB。 The simplest way to trigger UB is via __builtin_unreachable() .触发 UB 的最简单方法是通过__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.使用 c++14 在 gcc 7.2 和 clang 5.0 上编译得很好。 If you un-comment the call to foo(2,2) , gcc emits:如果取消注释对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");
     ^~~~~~~~~~~~~~

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.它不完全是static_assert ,因为编译器不会直接为您发出消息,但它确实让编译器指向正确的行并且消息将在调用堆栈中。

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 dmaIdstreamId是文字或constexpr (枚举类成员),整个函数只能在编译时工作

to pass dmaId and streamId as not-template parameter seems to me the wrong way.dmaIdstreamId作为非模板参数传递在我看来是错误的方式。

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.如果你可以在SpiDmaTxStreams枚举中添加一个特殊的错误值......比如说SpiDmaTxStreams::ErrorValue ......我提出了另一种解决方案,同样基于模板结构但具有恢复逻辑:非专用结构和static_error的单个专用版本消息。

I mean... if you return SpiDmaTxStreams::ErrorValue in case of unacceptable combination我的意思是......如果你在不可接受的组合的情况下返回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"
}

You can call spiDmaTxStream() to give value to a template value (caution: code not tested) as follows您可以调用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" );
 };

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.如果dmaId / streamId不可接受, spiDmaTxStream()返回SpiDmaTxStreams::ErrorValue ,因此foo专用版本被激活并且static_error()消息负责。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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