Are `inline` and `noexcept` redundant in a consteval context?
Asked Answered
P

1

10

I am working with some code in which constexpr functions are used which I currently refactor to be consteval whenever possible.

inline constexpr auto example() noexcept { /*...*/ }

As I understand it, the inline keyword above is already redundant in the constexpr function.

As far as I understand it, the noexcept keywords are redundant for consteval functions, because as I understand it consteval has to be evaluated at compile time, and hence implies noexcept. Is this true or might there be something I currently do not consider (like constexpr exceptions)?

consteval auto example() { /*...*/ }
Punctual answered 27/2, 2020 at 15:14 Comment(1)
Exceptions can't be thrown at compile-time, only at run-time. Therefore exception-specifiers have no meaning for consteval functions.Analisaanalise
O
12

inline is redundant because: [dcl.constexpr]/1:

A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable ([dcl.inline]).


noexcept is somewhat more interesting. It is true that an attempt to throw an exception (whether via throw, dynamic_cast, or typeid) is not allowed during constant evaluation time ([expr.const]/5.24). And since a consteval function is only evaluated during constant evaluation time, the noexcept has minimal benefit - an exception will certainly never escape this scope. And, moreover, you can't even have a function pointer to these functions leak to runtime either. There really is no mechanism I can think of for which this would be relevant.

However, perhaps at some point in the future, might we be allowed to have exceptions during constant evaluation? And if we go there, would it become important to know about noexcept-ness in that world? An exception that leaks out of a constant evaluation context would likely have to mean a compile error, so might you have algorithms that make different choices based on the noexcept-ness of a consteval function?

That said, I'd skip the noexcept. It certainly has no benefit now, and it may not even have benefit in the future. And if I end up being wrong then... I'm sorry.

Although really, as T.C. points out, it's not that simple:

consteval int current() { return 1; }
void f(int, int = current()) noexcept;

What's noexcept(f(1))? As written, it's false. And that's pretty weird right, since this the expression f(1) definitely isn't going to throw. So maybe you should write noexcept. Although since again it definitely can't throw, maybe the language should make consteval functions noexcept by default. Although if we do that, and do eventually add exceptions during constant evaluation time... now we're back to lying again since that's something we won't be able to claw back.

Openfaced answered 27/2, 2020 at 15:34 Comment(6)
Compile-time meta-programing-style algorithms deciding which branch to pick depending on constant-evaluation-throwing-different things sounds.. awful. Just awful. "Stack unwinding rules" during expression type deduction.. uuuh.. I hope noone ever gets an shot at defining such a feature. Now, thinking about it again - isn't SFINAE a sort of try-catch already? "cause any error, and template specialization resolution will pick a different path"? :)Anagnorisis
Isn't it throwing exception at runtime is blocked for nonexcept? example of factorial here.Allocation
@Allocation An exception cannot escape noexcept - if such a thing happens, the program is std::terminate()-ed. During constant evaluation, where any thrown exception is a compile error.Openfaced
It's really nice to make the noexcept operator not lie, however.Roulers
@T.C.: How is it a lie? noexcept tells whether there are any non-noexcept function calls in the given expression. A non-noexcept function called at compile-time is still non-noexcept. This may be even more important if we get static exceptions and the ability to throw them in constexpr code.Sector
In case it wasn't obvious, the name current was alluding to std::source_location::current, which is - correctly - marked noexcept. Weirdness of the result aside, the bigger problem is that omitting noexcept would make the function significantly less useful. Consider the case where f is actually a move constructor of some class.Roulers

© 2022 - 2024 — McMap. All rights reserved.