Document number: N4067
Date: 2014-06-20
Author: Daniel Krügler
Project: Programming Language C++, Library Evolution Working Group
Reply-to: Daniel Krügler

Experimental std::function etc.

Introduction

This paper provides wording to the effect that it creates independent fundamental-ts types in namespace std::experimental for function, promise, and packaged_task.

Discussion

Currently, the fundamental-ts refers to std::function and has applied extensions to that existing C++11 type, which has caused some significant concerns, which are in detailed described in N4041.

As a consequence of discussing this paper ("Guidelines for the contents of Technical Specifications") and performing straw polls within LEWG the following results had been obtained:

std::function, etc. changes in std::experimental?

SF-F-N-A-SA

5-8-5-0-1

This proposal attempts to provide wording to realize these effects.

Proposed resolution

The proposed wording changes refer to N4023.

  1. Add a new entry to Table 1 — "C++ library headers" as indicated:

    [Drafting note: Currently no header file <experimental/future> exists, but is needed to isolate the new types experimental::promise and experimental::packaged_task. — end drafting note]

    Table 1 — C++ library headers
    <experimental/future>
  2. Modify Table 2 — "Significant features in this technical specification" as indicated:

    [Drafting note: These changes assume that the assigned feature name suffix remains the same. — end drafting note]

    Table 2 — Significant features in this technical specification
    Doc.
    No.
    Title Primary
    Section
    Macro Name Suffix Value Header
    N3916 Type-erased allocator
    for std::function
    2.3 [mods.func.wrap]
    4.2 [exp.func.wrap.func]
    function_erased_allocator 201402 <functional>
    <experimental/functional>
    N3916 Type-erased allocator
    for std::promise
    2.6 [mods.futures.promise]
    ?.? [exp.futures.promise]
    promise_erased_allocator 201402 <future>
    <experimental/future>
    N3916 Type-erased allocator
    for std::packaged_task
    2.7 [mods.futures.task]
    ?.? [exp.futures.task]
    packaged_task_erased_allocator 201402 <future>
    <experimental/future>
  3. Remove the existing sub-clause 2.3 [mods.func.wrap] in its entirety:

    2.3 Additions to std::function [mods.func.wrap]

    -1- […]

    […]

    -22- Effects: Interchanges the targets of *this and other.

  4. Remove the existing sub-clause 2.6 [mods.futures.promise] in its entirety:

    2.6 Additions to std::promise [mods.futures.promise]

    -1- […]

    -2- and the following paragraph is inserted […]

  5. Remove the existing sub-clause 2.7 [mods.futures.task] in its entirety:

    2.7 Additions to std::packaged_task [mods.futures.task]

    -1- […]

    -2- and the following paragraph is inserted […]

  6. Change header <experimental/functional> synopsis, 4.1 [header.functional.synop], as indicated:

    [Drafting note: The following header synopsis fixes the missing noexcept specifications of the comparison functions as recently resolved by LWG 2401 yet. We consider this change editorial at this point, because the two different declarations were already in conflict and this change does not have impact on the actual prototype specifications, which will have the same specifiers as in C++14. — end drafting note]

    #include <functional>
    
    namespace std {
    namespace experimental {
    inline namespace fundamentals_v1 {
    […]
    // 4.2 Class template function:
    template<class> class function; // undefined
    template<class R, class... ArgTypes> class function<R(ArgTypes...)>;
    
    template<class R, class... ArgTypes>
    void swap(function<R(ArgTypes...)>&, function<R(ArgTypes...)>&);
    
    template<class R, class... ArgTypes>
    bool operator==(const function<R(ArgTypes...)>&, nullptr_t) noexcept;
    template<class R, class... ArgTypes>
    bool operator==(nullptr_t, const function<R(ArgTypes...)>&) noexcept;
    template<class R, class... ArgTypes>
    bool operator!=(const function<R(ArgTypes...)>&, nullptr_t) noexcept;
    template<class R, class... ArgTypes>
    bool operator!=(nullptr_t, const function<R(ArgTypes...)>&) noexcept;
    […]
    }
    }
    
    template<class R, class... ArgTypes, class Alloc>
    struct uses_allocator<experimental::function<R(ArgTypes...)>, Alloc>;
    
    }
    
  7. Between 4.1 [header.functional.synop] and 4.2 [func.searchers] insert the following new sub-clause as indicated:

    4.2 Class template function [exp.func.wrap.func]

    [Drafting note: The following class template synopsis fixes the missing noexcept specification of the nullptr_t assignment operator as recently resolved by LWG 2401. We consider this change as editorial, because the fundamental-ts specification refers otherwise to a non-existing declaration in C++14. — end drafting note]

    The specification of all declarations within this sub-clause 4.2 [exp.func.wrap.func] and its sub-clauses are the same as the corresponding declarations, as specified in C++14 §20.9.11.2, unless explicitly specified otherwise. [Note: std::experimental::function uses std::bad_function_call, there is no additional type std::experimental::bad_function_callend note].

    namespace std {
      namespace experimental {
      inline namespace fundamentals_v1 {
    
        template<class> class function; // undefined
        
        template<class R, class... ArgTypes>
        class function<R(ArgTypes...)> {
        public:
          typedef R result_type;
          typedef T1 argument_type;
          typedef T1 first_argument_type;
          typedef T2 second_argument_type;
    	  
          typedef erased_type allocator_type;
        
          function() noexcept;
          function(nullptr_t) noexcept;
          function(const function&);
          function(function&&);
          template<class F> function(F);
          template<class A> function(allocator_arg_t, const A&) noexcept;
          template<class A> function(allocator_arg_t, const A&,
            nullptr_t) noexcept;
          template<class A> function(allocator_arg_t, const A&,
            const function&);
          template<class A> function(allocator_arg_t, const A&,
            function&&);
          template<class F, class A> function(allocator_arg_t, const A&, F);
        
          function& operator=(const function&);
          function& operator=(function&&);
          function& operator=(nullptr_t) noexcept;
          template<class F> function& operator=(F&&);
          template<class F> function& operator=(reference_wrapper<F>);
    	  
          ~function();
    	  
          void swap(function&);
          template<class F, class A> void assign(F&&, const A&);
        
          explicit operator bool() const noexcept;
        
          R operator()(ArgTypes...) const;
        
          const type_info& target_type() const noexcept;
          template<class T> T* target() noexcept;
          template<class T> const T* target() const noexcept;
    
          pmr::memory_resource* get_memory_resource();
        };
        
        template <class R, class... ArgTypes>
        bool operator==(const function<R(ArgTypes...)>&, nullptr_t) noexcept;
        template <class R, class... ArgTypes>
        bool operator==(nullptr_t, const function<R(ArgTypes...)>&) noexcept;
        
        template <class R, class... ArgTypes>
        bool operator!=(const function<R(ArgTypes...)>&, nullptr_t) noexcept;
        template <class R, class... ArgTypes>
        bool operator!=(nullptr_t, const function<R(ArgTypes...)>&) noexcept;
        
        template <class R, class... ArgTypes>
        void swap(function<R(ArgTypes...)>&, function<R(ArgTypes...)>&);
    
      } // namespace fundamentals_v1
      } // namespace experimental
    
      template <class R, class... ArgTypes, class Alloc>
      struct uses_allocator<experimental::function<R(ArgTypes...)>, Alloc>
        : true_type { };
    
    } // namespace std
    
  8. Add the following new sub-clause as a children of 4.2 [exp.func.wrap.func] including its first paragraph, that replaces the corresponding specification provided by paragraph 1 of C++14 §20.9.11.2.1. Then add the following changed member specifications:

    4.2.1 function construct/copy/destroy [exp.func.wrap.func.con]

    [Drafting note: The following first paragraph has been slightly editorially improved to (a) make the difference between std::function and std::experimental::function clearer (which seems necessary due to the "including" wording that can be parsed in two different ways) and (b) to make intended normative wording clearer, that had been put into parenthesis before, by simply removing these parenthesis) — end drafting note]

    -?- When a function constructor that takes a first argument of type allocator_arg_t is invoked, the second argument is treated as a type-erased allocator (8.3 [memory.type.erased.allocator]). If the constructor moves or makes a copy of a function object (C++14 §20.9), including an instance of the experimental::function class template, then that move or copy is performed by using-allocator construction with allocator get_memory_resource().

    function& operator=(const function& f);
    

    -?- Effects: function(allocator_arg, get_memory_resource(), f).swap(*this);

    -?- Returns: *this

    function& operator=(function&& f);
    

    -?- Effects: function(allocator_arg, get_memory_resource(), std::move(f)).swap(*this);

    -?- Returns: *this

    [Drafting note: The following Effects element has been fixed and syncs the wording "*this != NULL" with the C++14 working draft replacing it with "*this != nullptr" — end drafting note]

    function& operator=(nullptr_t) noexcept;
    

    -?- Effects: If *this != nullptr, destroys the target of this.

    -?- Postconditions: !(*this).

    -?- Returns: *this

    template<class F> function& operator=(F&& f);
    

    -?- Effects: function(allocator_arg, get_memory_resource(), std::forward<F>(f)).swap(*this);

    -?- Returns: *this

    template<class F> function& operator=(reference_wrapper<F> f);
    

    -?- Effects: function(allocator_arg, get_memory_resource(), f).swap(*this);

    -?- Returns: *this

  9. Add the following new sub-clause as a children of 4.2 [exp.func.wrap.func] and add the following changed member specifications:

    4.2.2 function modifiers [exp.func.wrap.func.mod]

    void swap(function& other);
    

    [Drafting note: The previous Preconditions element has been replaced by a Requires element, see LWG 2395end drafting note]

    -?- Requires: this->get_memory_resource() == other->get_memory_resource().

    -?- Effects: Interchanges the targets of *this and other.

  10. Following 8 [memory], insert a new Clause [exp.future] and sub-clause [header.exp.future.synop] as indicated:

    Futures [exp.future]

    Header <experimental/future> synopsis [header.exp.future.synop]

    [Drafting note: An additional editorial fix is applied in the declaration of swap for packaged_taskend drafting note]

    #include <future>
    
    namespace std {
      namespace experimental {
      inline namespace fundamentals_v1 {
    
        template <class R> class promise;
        template <class R> class promise<R&>;
        template <> class promise<void>;
    
        template <class R>
        void swap(promise<R>& x, promise<R>& y) noexcept;
    
        template <class> class packaged_task; // undefined
        template <class R, class... ArgTypes>
        class packaged_task<R(ArgTypes...)>;
    
        template <class R, class... ArgTypes>
        void swap(packaged_task<R(ArgTypes...)>&, packaged_task<R(ArgTypes...)>&) noexcept;
    	
      } // namespace fundamentals_v1
      } // namespace experimental
    
      template <class R, class Alloc>
      struct uses_allocator<experimental::promise<R>, Alloc>;
    
      template <class R, class Alloc>
      struct uses_allocator<experimental::packaged_task<R>, Alloc>;  
    
    } // namespace std
    
  11. As children of the new Clause [exp.future], insert a new sub-clause [exp.futures.promise] as indicated:

    ?.? Class template promise [exp.futures.promise]

    The specification of all declarations within this sub-clause [exp.futures.promise] and its sub-clauses are the same as the corresponding declarations, as specified in C++14 §30.6.5, unless explicitly specified otherwise. — end note].

    namespace std {
      namespace experimental {
      inline namespace fundamentals_v1 {
    
        template <class R>
        class promise {
        public:
          typedef erased_type allocator_type;
    
          promise();
          template <class Allocator>
          promise(allocator_arg_t, const Allocator& a);
          promise(promise&& rhs) noexcept;
          promise(const promise& rhs) = delete;
          ~promise();
    	 
          promise& operator=(promise&& rhs) noexcept;
          promise& operator=(const promise& rhs) = delete;
          void swap(promise& other) noexcept;
    
          future<R> get_future();
    	  
          void set_value(see below);
          void set_exception(exception_ptr p);
    	  
          void set_value_at_thread_exit(const R& r);
          void set_value_at_thread_exit(see below);
          void set_exception_at_thread_exit(exception_ptr p);
    
          pmr::memory_resource* get_memory_resource();	
        };
        
        template <class R>
        void swap(promise<R>& x, promise<R>& y) noexcept;
    
      } // namespace fundamentals_v1
      } // namespace experimental
    
      template <class R, class Alloc>
      struct uses_allocator<experimental::promise<R>, Alloc>;
    
    } // namespace std
    

    When a promise constructor that takes a first argument of type allocator_arg_t is invoked, the second argument is treated as a type-erased allocator (8.3).

  12. As children of the new Clause [exp.future], insert a new sub-clause [exp.futures.task] as indicated:

    ?.? Class template packaged_task [exp.futures.task]

    The specification of all declarations within this sub-clause [exp.futures.task] and its sub-clauses are the same as the corresponding declarations, as specified in C++14 §30.6.9, unless explicitly specified otherwise. — end note].

    [Drafting note: Contrary to C++14 the repeated declaration of the primary packaged_task seems unnecessary here, because the same thing is already said in the header synopsis — end drafting note]

    namespace std {
      namespace experimental {
      inline namespace fundamentals_v1 {
    
        template <class R, class... ArgTypes>
        class packaged_task<R(ArgTypes...)> {
        public:
          typedef erased_type allocator_type;
    
          packaged_task() noexcept;
          template <class F>
          explicit packaged_task(F&& f);
          template <class F, class Allocator>
          explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);
          ~packaged_task();
          	 
          packaged_task(const packaged_task&) = delete;
          packaged_task& operator=(const packaged_task&) = delete;
          
          packaged_task(packaged_task&& rhs) noexcept;
          packaged_task& operator=(packaged_task&& rhs) noexcept;
          void swap(packaged_task& other) noexcept;
          	  
          bool valid() const noexcept;
          	  
          future<R> get_future();
          
          void operator()(ArgTypes... );
          void make_ready_at_thread_exit(ArgTypes...);
          
          void reset();
    	  
          pmr::memory_resource* get_memory_resource();	
        };
        
        template <class R, class... ArgTypes>
        void swap(packaged_task<R(ArgTypes...)>&, packaged_task<R(ArgTypes...)>&) noexcept;
    
      } // namespace fundamentals_v1
      } // namespace experimental
    
      template <class R, class Alloc>
      struct uses_allocator<experimental::packaged_task<R>, Alloc>;
    
    } // namespace std
    

    When a packaged_task constructor that takes a first argument of type allocator_arg_t is invoked, the second argument is treated as a type-erased allocator (8.3).