2389. [fund.ts] function::operator= is over-specified and handles allocators incorrectly

Section: 99 [fund.ts::func.wrap.func.con] Status: TS Submitter: Pablo Halpern Opened: 2014-05-23 Last modified: 2017-07-30

Priority: 2

View all issues with TS status.

Discussion:

Addresses: fund.ts

This issue against the TS is similar to LWG 2386, which is against the standard. The Effects clauses for the assignment operator for class template function are written as code that constructs a temporary function and then swaps it with *this. The intention appears to be that assignment should have the strong exception guarantee, i.e., *this is not modified if an exception is thrown. The description in the standard is incorrect when *this was originally constructed using an allocator. The TS attempts to correct the problem, but the correction is incomplete.

The wording in the TS uses get_memory_resource() to construct a temporary function object with the same allocator as the left-hand size (lhs) of the assignment. The intended result of using this pattern was that the allocator for *this would be unchanged, but it doesn't quite work. The problem is that the allocator returned by get_memory_resource() is not the same type as the type-erased allocator used to construct the function object, but rather a type-erased distillation of that type that is insufficient for making a true copy of the allocator. The rules for type-erased allocators in the TS ([memory.type.erased.allocator]) specify that the lifetime of the object returned by get_memory_resource() is sometimes tied to the lifetime of *this, which might cause the (single copy of) the allocator to be destroyed if the swap operation destroys and reconstructs *this, as some implementations do (and are allowed to do).

The desired behavior is that assignment would leave the allocator of the lhs unchanged. The way to achieve this behavior is to construct the temporary function using the original allocator. Unfortunately, we cannot describe the desired behavior in pure code, because get_memory_resource() does not really name the type-erased allocator, as mentioned above. The PR below, therefore, uses pseudo-code, inventing a fictitious ALLOCATOR_OF(f) expression that evaluates to the actual allocator type, even if that allocator was type erased. I have implemented this PR successfully.

[2014-06-21, Rapperswil]

Apply to Library Fundamentals TS (after removing the previous "Throws: Nothing" element to prevent an editorial conflict with 2401).

Proposed resolution:

This wording is relative to N3908.

  1. Change in [mods.func.wrap] in the Library TS as indicated:

    In the following descriptions, let ALLOCATOR_OF(f) be the allocator specified in the construction of function f, or allocator<char>() if no allocator was specified.

    function& operator=(const function& f);
    

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

    […]

    function& operator=(function&& f);
    

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

    […]

    function& operator=(nullptr_t);
    

    -11- Effects: If *this != NULL, destroys the target of this.

    -12- Postconditions: !(*this). The memory resource returned by get_memory_resource() after the assignment is equivalent to the memory resource before the assignment. [Note: the address returned by get_memory_resource() might change — end note]

    -13- Returns: *this

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

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

    […]

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

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

    […]