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() { /*...*/ }
valarMorghulis
  • 302
  • 1
  • 12
mutableVoid
  • 1,284
  • 2
  • 11
  • 29

1 Answers1

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    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"? :) – quetzalcoatl Feb 27 '20 at 15:41
  • Isn't it throwing exception at runtime is blocked for `nonexcept`? example of [factorial](https://en.cppreference.com/w/cpp/language/constexpr) here. – TruthSeeker Feb 27 '20 at 16:17
  • @TruthSeeker 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. – Barry Feb 27 '20 at 16:29
  • 2
    It's really nice to make the `noexcept` operator not lie, however. – T.C. Feb 27 '20 at 16:55
  • 1
    @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. – Nicol Bolas Feb 27 '20 at 19:06
  • 1
    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. – T.C. Feb 27 '20 at 20:21