[英]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
,因为编译器不会直接为您发出消息,但它确实让编译器指向正确的行并且消息将在调用堆栈中。
对不起,因为你问了一个完全不同的解决方案,但如果
dmaId
和streamId
是文字或constexpr
(枚举类成员),整个函数只能在编译时工作
将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;
如果你可以在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.