2

Disclaimer: since this is likely to become an opinion based topic I would like to focus more on the factual side of things.

Prologue

In C++, we have the noexcept specifier to declare that a function may (or may not) throw an expection. Furthermore, we have the noexcept operator that tells us whether an expression can throw an exception or not.

Especially in TMP it can be a pain to specify whether a function can or cannot throw. Take std::invoke for example:

template< class F, class... Args>
constexpr std::invoke_result_t<F, Args...> invoke(F&& f, Args&&... args) noexcept(noexcept(std::is_nothrow_invocable_v<F, Args...>));

It even needs a type trait for what basically results in:

auto invoke(F f, Args... args) noexcept(noexcept(f(args...)))
{
    return f(args...);
}

Interestingly enough, the MSVC implementation even reverses the relationship between std::invoke and std::is_nothrow_invocable, basically doing

std::is_nothrow_invocable = noexcept(std::invoke(F, Args...))

and having a myriad of overloads for std::invoke. Lets take a look at the case that handles a normal function call (e.g. lambda or namespace function):

template <class _Callable, class... _Types>
CONSTEXPR auto _CONCAT(NAME_PREFIX, invoke)(_Callable && _Obj, _Types && ... _Args)
noexcept(noexcept(_CONCAT(NAME_PREFIX, _Invoker) < _Callable, _Types... > ::_Call(_STD forward<_Callable>(_Obj), _STD forward<_Types>(_Args)...))) 
-> decltype(_CONCAT(NAME_PREFIX, _Invoker) < _Callable, _Types... > ::_Call(_STD forward<_Callable>(_Obj), _STD forward<_Types>(_Args)...)) /* INVOKE a callable object */
{
    return _CONCAT(NAME_PREFIX, _Invoker)<_Callable, _Types...>::_Call(_STD forward<_Callable>(_Obj), _STD forward<_Types>(_Args)...);
}

There are a lot of macros involved here but the gist of it is, that the function body has to be repeated 3 times:

  1. to accurately declare noexcept
  2. to specify the return type (not sure why it's done that way, maybe to be explicit about it)
  3. the function body itself

Proposal

Now lets assume we extented the noexcept specifier with the following syntax:

noexcept(auto)

The behavior is simple: resolve to noexcept(true) if, and only if, noexcept(expr) evaluates to true for each expression expr in the function's body. Otherwise noexcept(false).

I'm not that familiar with the standard so I'm not sure if the term expression is too loosely here.

The pseudo implementation above

auto invoke(F f, Args... args) noexcept(noexcept(f(args...)))
{
    return f(args...);
}

would then become

auto invoke(F f, Args... args) noexcept(auto)
{
    return f(args...);
}

Epilogue

I've only used std::invoke as an example here but I'm certain there are more functions in the standard library (and in the wild) that could benefit from such a feature, especially in TMP.

Now to the questions:

  1. I don't see any compatibility issues if such a feature was added but I'm just a dude, so are there any problems in this regard?
  2. What are the caveats/disadvantages of this proposal?
  3. (optional) Where else would this come in handy within the standard library?

My last question is about compiler optimizations. After reading When should I really use noexcept? (especially the second answer) I was wondering what the compiler does if we declare a function noexcept and throw inside that function. I know the standard requires a call to std::terminate in this case, but how does a compiler implement this? I assume the compiler does something like this (going back to the std::invoke example):

auto invoke(F f, Args... args) noexcept(noexcept(f(args...)))
{
    try
    {
       return f(args...);
    }
    catch
    {
        std::terminate();
    }
}

I'm far from beeing an expert on optimizations so I don't know how much the compiler optimizes, but I assume it's a lot. Nevertheless, the compiler is only required to do that handling because we could have lied when declaring our function noexcept. However, if we let the compiler deduce the noexceptness it knows for certain and therefore doesn't need to catch anything. On the other hand, I assume the compiler already does that regardless. So

  1. Would this proposal affect performance?
Timo
  • 9,269
  • 2
  • 28
  • 58
  • 2
    You can't detect at compile time whether a function will throw an exception. It may call into pre-compiled library code that the compiler cannot see, which may or may not throw. Which means that your function may or may not throw. – Jesper Juhl Feb 09 '20 at 17:15
  • 1
    @JesperJuhl That's true, but what the compiler can do is check if a function is declared `noexcept` (since it is part of the function type). I'm not trying (or claiming) to detect if a function may or may not throw. One could call my idea _perfect forwarding of `noexcept`_. – Timo Feb 09 '20 at 17:22
  • Looks like a sound idea. If all called functions are declared `noexcept` and I don't throw explicitly, this function should also be `noexcept`. – Aykhan Hagverdili Feb 09 '20 at 17:25
  • 1
    Does the answer to the question [Is there an automatic noexcept specifier?](https://stackoverflow.com/questions/30456801/is-there-an-automatic-noexcept-specifier) answer your question? – L. F. Feb 10 '20 at 10:36
  • @L.F. lol even the same syntax. Thanks. – Timo Feb 10 '20 at 10:38

0 Answers0