9

I am trying to understand the noexcept feature. I know it could be confusing, but besides that could noexcept be deduced from the calling function when possible.

This is a non working example of this situation,

void f(){}
void f() noexcept{} // not allowed in c++

void g(){f();} // should call f
void h() noexcept{f();} // should call f noexcept
int main(){
    g();
    h();
}

If there is no try/catch block in the calling function (h) then the compiler could deduce that one is interested in calling a particular f.

Is this pattern used in some other workaround form?

All I can imagine is somthing like this but it is not very generic:

template<bool NE> void F() noexcept(NE);

template<>
void F<true>() noexcept(true){}
template<>
void F<false>() noexcept(false){}

void g(){F<noexcept(g)>();} // calls F<false>
void h() noexcept{F<noexcept(h)>();} // call F<true>

Some may wonder why that would make sense. My logic is that that C++ allows to overload with respect to const, both a argument of functions and a member functions. const member functions prefer to call const member overloads for example.

I think it would make sense for noexcept functions to call noexcept "overloads". Specially if they are not called from a try/catch block.

alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    With c++17 you could prolly use `if constexpr` as in: `template void F() noexcept(NE) { if constexpr(NE) { ... } else { ... } }`. Not sure if it's of any use to your question. – Super-intelligent Shade Jan 18 '18 at 19:59
  • 1
    What's the use case for such overloading? Either whatever `f` is doing cannot fail, in which case there's no need for the `noexcept(false)` version, or it can fail and so the `noexcept(true)` version needs some way to report that failure, which typically means a different signature. – T.C. Jan 18 '18 at 20:54
  • 1
    "If there is no try/catch block in the calling function (h) then the compiler could deduce that one is interested in calling a particular f." Most exception-safe code doesn't have a try block; it relies on RAII. It will still want to call the noexcept(false) version. – Todd Fleming Jan 18 '18 at 21:09
  • 4
    If you want to have to versions, one reporting error by return-value, and the other by exception, take a look at `std::nothrow`, and how it is used. – Deduplicator Jan 18 '18 at 21:26
  • 1
    @Deduplicator, excellent. Didn't know about the `std::nothrow`, that seems to be the idiom to follow. – alfC Jan 18 '18 at 22:22
  • @T.C., to begin with, the implementations can be different. noexcept won't mean that it cannot fail, it will mean that it can fail but not through execptions (but with abort). I think the example in cppreference for `new` in Deduplicator comment is very illustrative. – alfC Jan 18 '18 at 22:25
  • @InnocentBystander interesting, but NE will no be deduced, right? – alfC Jan 20 '18 at 06:44
  • @alfC, that's right. – Super-intelligent Shade Jan 20 '18 at 21:26
  • 1
    @alfC, fwiw the [asio library](https://think-async.com/Asio/Documentation) uses similar approach to `std::nothrow`. It has 2 overloads for many functions, one of which is noexcept and returns an error code; the other -- throws an exception. – Super-intelligent Shade Jan 20 '18 at 21:30
  • @InnocentBystander thanks for the info. I wonder if one ends up calling nothrow functions from nothrow functions (and without the try catch enclosing), so it would be nice to automatically choose the correct one. – alfC Jan 21 '18 at 00:43

2 Answers2

4

It makes sense,

Of course it would make sense in principle. One version of the function could run, say, a faster algorithm, but which requires dynamically-allocated extra scratch memory, while the noexcept version could use a slower algorithm with O(1) extra space, on the stack.

but wouldn't be able to resolve the overload ...

As you may know, it's perfectly valid to call noexcept(false) functions from noexcept(true) functions. You're just risking a terminate instead of an exception being thrown; and sometimes - you're not risking anything because you've verified that the inputs you pass cannot trigger an exception. So, how would the compiler know which version of the function you're calling? And the same question for the other direction - maybe you want to call your noexcept(true) function from within a noexcept(false) function? That's also allowed.

... and - it would be mostly syntactic sugar anyway

With C++11, you can write:

#include <stdexcept>

template <bool ne2>
int bar(int x) noexcept(ne2);

template<> int bar<true>(int) noexcept { return 0; }
template<> int bar<false>(int) { throw std::logic_error("error!"); }

and this compiles just fine: GodBolt.

So you can have two function with the same name and same arguments, differing only w.r.t. their noexcept value - but with different template arguments.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
1

I don't think overloading on noexcept makes a lot sense on its own. For sure, it makes sense wether your function f is noexcept, especially when called from h, as h needs to catch the possible exception and call std::abort.

However, just overloading on noexcept ain't a good thing to do. It's like disabling exceptions in the standard library. I'm not arguing you shouldn't do that, though, you do loose functionality because of it. For example: std::vector::at throws if the index is invalid. If you disable exceptions, you don't have an alternative for using this functionality.

So if you really want to have 2 versions, you might want to use other alternatives to indicate failure. std::optional, std::expected, std::error_code ...

Even if you manage to overload on noexcept, your function will have a different return type. This ain't something I would expect as a user from your framework.

Hence, I think it's better to overload is a different way, so the user can choose which variant to use, this by using the boolean explicitly, std::nothrow as argument output argument with std::error_code. Or maybe, you should make a choice on the error handling strategy you use in your library and enforce that to your users.

JVApen
  • 11,008
  • 5
  • 31
  • 67