1

In this question I consider the libstdc++ implementation of the _Not_fn call wrapper.

It defines four overloads of function call operator as follows:

 #define _GLIBCXX_NOT_FN_CALL_OP( _QUALS )                          \
       template<typename... _Args>                                  \
    decltype(_S_not<__inv_res_t<_Fn _QUALS, _Args...>>())           \
    operator()(_Args&&... __args) _QUALS                            \
    noexcept(noexcept(_S_not<__inv_res_t<_Fn _QUALS, _Args...>>())) \
    {                                                               \
      return !std::__invoke(std::forward< _Fn _QUALS >(_M_fn),      \
                            std::forward<_Args>(__args)...);        \
    }
       _GLIBCXX_NOT_FN_CALL_OP( & )
       _GLIBCXX_NOT_FN_CALL_OP( const & )
       _GLIBCXX_NOT_FN_CALL_OP( && )
       _GLIBCXX_NOT_FN_CALL_OP( const && )
 #undef _GLIBCXX_NOT_FN_CALL

It is easy to see, that noexcept specification is set as:

noexcept(noexcept(_S_not<__inv_res_t<_Fn _QUALS, _Args...>>()))

where __inv_res_t is an alias template:

template<typename _Fn2, typename... _Args>
using __inv_res_t = typename __invoke_result<_Fn2, _Args...>::type;

and _S_not is a static member function template:

template<typename _Tp>
static decltype(!std::declval<_Tp>())
_S_not() noexcept(noexcept(!std::declval<_Tp>()));

Now, following the logic behind noexcept specification I conclude, that:

  1. Function call operator conceptually has the same noexcept specification as _S_not<__inv_res_t<_Fn _QUALS, _Args...>>.
  2. _S_not<__inv_res_t<_Fn _QUALS, _Args...>> is marked as noexcept depending on whether a negation applied to the result of std::__invoke(...) is noexcept.

From my point of view, this noexcept specification doesn't cover the case when the callable object, wrapped into not_fn, may or may not throw itself while being invoked with a particular set of arguments passed to not_fn function call operator. In other words, it is not checked if std::__invoke(...) itself inside a function call operator might throw or not.

Do I miss something in this implementation?

The implementation from cppreference.com has a bit simpler noexcept specification. However, this implementation doesn't work with the latest g++ due to a known issue.

Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67

1 Answers1

2

There is actually no requirement that not_fn propagate noexcept. It is specified in [func.not_fn], each of the four call operators just looks something like:

template<class... Args>
  auto operator()(Args&&...) const&
    -> decltype(!declval<invoke_result_t<const FD&, Args...>>());

No noexcept. That said, P0356 proposes to add it and the current noexcept specifier makes no sense and could cause harm by being wrong, so filed 87538.


Update: this has been fixed for 7.4, 8.3 and 9.1.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks, Barry! Somehow, I overlooked the fact that the standard doesn't impose a noexcept restriction on not_fn function call operator. Current noexcept specifier doesn't make much sense for me either, so it is great to have a bug being opened! – Edgar Rokjān Oct 05 '18 at 22:16