繁体   English   中英

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

[英]Cleanly report compile-time error from constexpr without exceptions?

我正在寻找一种从 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"
}

触发 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);
}

使用 c++14 在 gcc 7.2 和 clang 5.0 上编译得很好。 如果取消注释对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 ,因为编译器不会直接为您发出消息,但它确实让编译器指向正确的行并且消息将在调用堆栈中。

对不起,因为你问了一个完全不同的解决方案,但如果

dmaIdstreamId是文字或constexpr (枚举类成员),整个函数只能在编译时工作

dmaIdstreamId作为非模板参数传递在我看来是错误的方式。

在我看来,以下内容要简单得多(抱歉:代码未测试)

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

如果你可以在SpiDmaTxStreams枚举中添加一个特殊的错误值......比如说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()消息负责。

暂无
暂无

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

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