function::operator=
is over-specified and handles allocators incorrectlySection: 4.2.1 [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.
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.
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 offunction
f
, orallocator<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
-12- Postconditions:*this != NULL
, destroys the target ofthis
.!(*this)
. The memory resource returned byget_memory_resource()
after the assignment is equivalent to the memory resource before the assignment. [Note: the address returned byget_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);