Reputation: 1098
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"
}
Upvotes: 4
Views: 2057
Reputation: 303377
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.
Upvotes: 3
Reputation: 66240
Sorry, because you asked a completely different solution, but if
dmaId
andstreamId
are literals orconstexpr
(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;
Upvotes: -1
Reputation: 66240
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.
Upvotes: -1