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:
- Function call operator conceptually has the same noexcept specification as
_S_not<__inv_res_t<_Fn _QUALS, _Args...>>
. _S_not<__inv_res_t<_Fn _QUALS, _Args...>>
is marked as noexcept depending on whether a negation applied to the result ofstd::__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.