On Fri, Apr 25, 2025 at 12:20 AM Jonathan Wakely <jwak...@redhat.com> wrote:

> Moving the static_assert that checks is_invocable_r_v into _Task_state
> means it is checked when we instantiate that class template.
>
> Replacing the __create_task_state function with a static member function
> _Task_state::_S_create ensures we instantiate _Task_state and trigger
> the static_assert immediately, not deep inside the implementation of
> std::allocate_shared. This results in shorter diagnostics that don't
> show deeply-nested template instantiations before the static_assert
> failure.
>
> Placing the static_assert at class scope also helps us to fail earlier
> than waiting until when the _Task_state::_M_run virtual function is
> instantiated. That also makes the diagnostics shorter and easier to read
> (although for C++11 and C++14 modes the class-scope static_assert
> doesn't check is_invocable_r, so dangling references aren't detected
> until _M_run is instantiated).
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/future (__future_base::_Task_state): Check
>         invocable requirement here.
>         (__future_base::_Task_state::_S_create): New static member
>         function.
>         (__future_base::_Task_state::_M_reset): Use _S_create.
>         (__create_task_state): Remove.
>         (packaged_task): Use _Task_state::_S_create instead of
>         __create_task_state.
>         * testsuite/30_threads/packaged_task/cons/dangling_ref.cc:
>         Adjust dg-error patterns.
>         * testsuite/30_threads/packaged_task/cons/lwg4154_neg.cc:
>         Likewise.
> ---
>
> Tested x86_64-linux.
>
LGTM.

>
>  libstdc++-v3/include/std/future               | 66 +++++++++----------
>  .../packaged_task/cons/dangling_ref.cc        |  3 +-
>  .../packaged_task/cons/lwg4154_neg.cc         | 10 +--
>  3 files changed, 38 insertions(+), 41 deletions(-)
>
> diff --git a/libstdc++-v3/include/std/future
> b/libstdc++-v3/include/std/future
> index b7ab233b85f..080690064a9 100644
> --- a/libstdc++-v3/include/std/future
> +++ b/libstdc++-v3/include/std/future
> @@ -1486,12 +1486,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      struct __future_base::_Task_state<_Fn, _Alloc, _Res(_Args...)> final
>      : __future_base::_Task_state_base<_Res(_Args...)>
>      {
> +#ifdef __cpp_lib_is_invocable // C++ >= 17
> +      static_assert(is_invocable_r_v<_Res, _Fn&, _Args...>);
> +#else
> +      static_assert(__is_invocable<_Fn&, _Args...>::value,
> +                   "_Fn& is invocable with _Args...");
> +#endif
> +
>        template<typename _Fn2>
>         _Task_state(_Fn2&& __fn, const _Alloc& __a)
>         : _Task_state_base<_Res(_Args...)>(__a),
>           _M_impl(std::forward<_Fn2>(__fn), __a)
>         { }
>
> +      template<typename _Fn2>
> +       static shared_ptr<_Task_state_base<_Res(_Args...)>>
> +       _S_create(_Fn2&& __fn, const _Alloc& __a)
> +       {
> +         return std::allocate_shared<_Task_state>(__a,
> +
> std::forward<_Fn2>(__fn),
> +                                                  __a);
> +       }
> +
>      private:
>        virtual void
>        _M_run(_Args&&... __args)
> @@ -1515,7 +1531,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        }
>
>        virtual shared_ptr<_Task_state_base<_Res(_Args...)>>
> -      _M_reset();
> +      _M_reset()
> +      { return _S_create(std::move(_M_impl._M_fn), _M_impl); }
>
>        struct _Impl : _Alloc
>        {
> @@ -1525,38 +1542,27 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         _Fn _M_fn;
>        } _M_impl;
>      };
> -
> -  template<typename _Signature, typename _Fn,
> -          typename _Alloc = std::allocator<int>>
> -    shared_ptr<__future_base::_Task_state_base<_Signature>>
> -    __create_task_state(_Fn&& __fn, const _Alloc& __a = _Alloc())
> -    {
> -      typedef typename decay<_Fn>::type _Fn2;
> -      typedef __future_base::_Task_state<_Fn2, _Alloc, _Signature> _State;
> -      return std::allocate_shared<_State>(__a, std::forward<_Fn>(__fn),
> __a);
> -    }
> -
> -  template<typename _Fn, typename _Alloc, typename _Res, typename...
> _Args>
> -    shared_ptr<__future_base::_Task_state_base<_Res(_Args...)>>
> -    __future_base::_Task_state<_Fn, _Alloc, _Res(_Args...)>::_M_reset()
> -    {
> -      return __create_task_state<_Res(_Args...)>(std::move(_M_impl._M_fn),
> -
> static_cast<_Alloc&>(_M_impl));
> -    }
>    /// @endcond
>
>    /// packaged_task
>    template<typename _Res, typename... _ArgTypes>
>      class packaged_task<_Res(_ArgTypes...)>
>      {
> -      typedef __future_base::_Task_state_base<_Res(_ArgTypes...)>
> _State_type;
> +      using _State_type =
> __future_base::_Task_state_base<_Res(_ArgTypes...)>;
>        shared_ptr<_State_type>                   _M_state;
>
>        // _GLIBCXX_RESOLVE_LIB_DEFECTS
>        // 3039. Unnecessary decay in thread and packaged_task
>        template<typename _Fn, typename _Fn2 = __remove_cvref_t<_Fn>>
> -       using __not_same
> -         = typename enable_if<!is_same<packaged_task, _Fn2>::value>::type;
> +       using __not_same = __enable_if_t<!is_same<packaged_task,
> _Fn2>::value>;
> +
> +      // _GLIBCXX_RESOLVE_LIB_DEFECTS
> +      // 4154. The Mandates for std::packaged_task's constructor
> +      // from a callable entity should consider decaying.
> +      template<typename _Fn, typename _Alloc = std::allocator<int>>
> +       using _Task_state
> +         = __future_base::_Task_state<__decay_t<_Fn>, _Alloc,
> +                                      _Res(_ArgTypes...)>;
>
>      public:
>        // Construction and destruction
> @@ -1565,16 +1571,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        template<typename _Fn, typename = __not_same<_Fn>>
>         explicit
>         packaged_task(_Fn&& __fn)
> -       : _M_state(
> -
>  __create_task_state<_Res(_ArgTypes...)>(std::forward<_Fn>(__fn)))
> -       {
> -#ifdef __cpp_lib_is_invocable // C++ >= 17
> -         // _GLIBCXX_RESOLVE_LIB_DEFECTS
> -         // 4154. The Mandates for std::packaged_task's constructor
> -         // from a callable entity should consider decaying
> -         static_assert(is_invocable_r_v<_Res, decay_t<_Fn>&,
> _ArgTypes...>);
> -#endif
> -       }
> +       : _M_state(_Task_state<_Fn>::_S_create(std::forward<_Fn>(__fn),
> {}))
> +       { }
>
>  #if __cplusplus < 201703L
>        // _GLIBCXX_RESOLVE_LIB_DEFECTS
> @@ -1583,8 +1581,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        // 2921. packaged_task and type-erased allocators
>        template<typename _Fn, typename _Alloc, typename = __not_same<_Fn>>
>         packaged_task(allocator_arg_t, const _Alloc& __a, _Fn&& __fn)
> -       : _M_state(__create_task_state<_Res(_ArgTypes...)>(
> -                  std::forward<_Fn>(__fn), __a))
> +       : _M_state(_Task_state<_Fn,
> _Alloc>::_S_create(std::forward<_Fn>(__fn),
> +                                                      __a))
>         { }
>
>        // _GLIBCXX_RESOLVE_LIB_DEFECTS
> diff --git
> a/libstdc++-v3/testsuite/30_threads/packaged_task/cons/dangling_ref.cc
> b/libstdc++-v3/testsuite/30_threads/packaged_task/cons/dangling_ref.cc
> index 51c6ade91c3..8cc3f78da9f 100644
> --- a/libstdc++-v3/testsuite/30_threads/packaged_task/cons/dangling_ref.cc
> +++ b/libstdc++-v3/testsuite/30_threads/packaged_task/cons/dangling_ref.cc
> @@ -8,6 +8,5 @@ int f();
>  std::packaged_task<const int&()> task(f);
>  // { dg-error "dangling reference" "" { target { c++14_down } } 0 }
>  // { dg-error "reference to temporary" "" { target { c++14_down } } 0 }
> -// { dg-error "no matching function" "" { target c++17 } 0 }
> -// { dg-error "enable_if" "" { target c++17 } 0 }
>  // { dg-error "static assertion failed" "" { target c++17 } 0 }
> +// { dg-error "note: .*std::is_invocable_r" "" { target c++17 } 0 }
> diff --git
> a/libstdc++-v3/testsuite/30_threads/packaged_task/cons/lwg4154_neg.cc
> b/libstdc++-v3/testsuite/30_threads/packaged_task/cons/lwg4154_neg.cc
> index 6ba1bb1a53e..b3413c2d11a 100644
> --- a/libstdc++-v3/testsuite/30_threads/packaged_task/cons/lwg4154_neg.cc
> +++ b/libstdc++-v3/testsuite/30_threads/packaged_task/cons/lwg4154_neg.cc
> @@ -12,16 +12,16 @@ struct F {
>
>  // Mandates: is_invocable_r_v<R, decay_t<F>&, ArgTypes...> is true.
>  const F f;
> -std::packaged_task<void()> p(f); // { dg-error "here" "" { target c++17 }
> }
> -// { dg-error "static assertion failed" "" { target c++17 } 0 }
> -// { dg-error "invoke_r" "" { target *-*-* } 0 }
> -// { dg-prune-output "enable_if<false" }
> +std::packaged_task<void()> p(f); // { dg-error "here" }
> +// { dg-error "static assertion failed" "" { target *-*-* } 0 }
> +// { dg-error "note: .*std::is_invocable_r_v<void, " "" { target c++17 }
> 0 }
>
>  // Only callable as rvalue
>  struct Frv {
>    int* operator()() && { return 0; }
>  };
> -std::packaged_task<int*()> p2(Frv{}); // { dg-error "here" "" { target
> c++17 } }
> +std::packaged_task<int*()> p2(Frv{}); // { dg-error "here" }
> +// { dg-error "note: .*std::is_invocable_r_v<int., " "" { target c++17 }
> 0 }
>
>  // Only callable as non-const lvalue
>  struct Fnc {
> --
> 2.49.0
>
>

Reply via email to