This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.
noexcept
in std::function
Section: 22.10.17.3 [func.wrap.func] Status: Resolved Submitter: Pablo Halpern Opened: 2014-02-27 Last modified: 2020-09-06
Priority: 3
View all other issues in [func.wrap.func].
View all issues with Resolved status.
Discussion:
The following constructors in 22.10.17.3 [func.wrap.func] are declared noexcept
, even
though it is not possible for an implementation to guarantee that they will not throw:
template <class A> function(allocator_arg_t, const A&) noexcept; template <class A> function(allocator_arg_t, const A&, nullptr_t) noexcept;
In addition, the following functions are guaranteed not to throw if the target
is a function pointer or a reference_wrapper
:
template <class A> function(allocator_arg_t, const A& a, const function& f); template <class F, class A> function(allocator_arg_t, const A& a, F f);
In all of the above cases, the function object might need to allocate memory
(an operation that can throw) in order to hold a copy of the type-erased
allocator itself. The first two constructors produce an empty function
object, but the allocator is still needed in case the object is later assigned
to. In this case, we note that the propagation of allocators on assignment is
underspecified for std::function
. There are three possibilities:
The allocator is never copied on copy-assignment, moved on move-assignment, or swapped on swap.
The allocator is always copied on copy-assignment, moved on move-assignment, and swapped on swap.
Whether or not the allocator is copied, moved, or swapped is determined at
run-time based on the propagate_on_container_copy_assignment
and
propagate_on_container_move_assignment
traits of the allocators at
construction of the source function, the target function, or both.
Although the third option seems to be the most consistent with existing wording in the containers section of the standard, it is problematic in a number of respects. To begin with, the propagation behavior is determined at run time based on a pair of type-erased allocators, instead of at compile time. Such run-time logic is not consistent with the rest of the standard and is hard to reason about. Additionally, there are two allocator types involved, rather than one. Any set of rules that attempts to rationally interpret the propagation traits of both allocators is likely to be arcane at best, and subtly wrong for some set of codes at worst.
The second option is a non-starter. Historically, and in the vast majority of existing code, an allocator does not change after an object is constructed. The second option, if adopted, would undermine the programmer's ability to construct, e.g., an array of function objects, all using the same allocator.
The first option is (in Pablo's opinion) the simplest and best. It is consistent with historical use of allocators, is easy to understand, and requires minimal wording. It is also consistent with the wording in N3916, which formalizes type-erased allocators.
For cross-referencing purposes: The resolution of this issue should be
harmonized with any resolution to LWG 2062(i), which questions the noexcept
specification on the following member functions of std::function:
template <class F> function& operator=(reference_wrapper<F>) noexcept; void swap(function&) noexcept;
[2015-05 Lenexa]
MC: change to P3 and status to open.
STL: note thatnoexcept
is an issue and large chunks of allocator should be destroyed.
[2015-12-16, Daniel comments]
See 2564(i) for a corresponding issue addressing library fundamentals v2.
Previous resolution [SUPERSEDED]:
This wording is relative to N3936.
Change 22.10.17.3 [func.wrap.func], class template
function
synopsis, as indicated:template <class A> function(allocator_arg_t, const A&)noexcept; template <class A> function(allocator_arg_t, const A&, nullptr_t)noexcept;Change 22.10.17.3.2 [func.wrap.func.con] as indicated:
-1- When any function constructor that takes a first argument of type
allocator_arg_t
is invoked, the second argument shall have a type that conforms to the requirements forAllocator
(Table 17.6.3.5). A copy of the allocator argument is used to allocate memory, if necessary, for the internal data structures of the constructed function object. For the remaining constructors, an instance ofallocator<T>
, for some suitable typeT
, is used to allocate memory, if necessary, for the internal data structures of the constructed function object.function() noexcept; template <class A> function(allocator_arg_t, const A&)noexcept;-2- Postconditions:
!*this
.function(nullptr_t) noexcept; template <class A> function(allocator_arg_t, const A&, nullptr_t)noexcept;-3- Postconditions:
!*this
.function(const function& f);template <class A> function(allocator_arg_t, const A& a, const function& f);-4- Postconditions:
!*this
if!f
; otherwise,*this
targets a copy off.target()
.-5- Throws: shall not throw exceptions if
f
's target is a callable object passed viareference_wrapper
or a function pointer. Otherwise, may throwbad_alloc
or any exception thrown by the copy constructor of the stored callable object. [Note: Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, wheref
's target is an object holding only a pointer or reference to an object and a member function pointer. — end note]template <class A> function(allocator_arg_t, const A& a, const function& f);-?- Postconditions:
!*this
if!f
; otherwise,*this
targets a copy off.target()
.function(function&& f); template <class A> function(allocator_arg_t, const A& a, function&& f);-6- Effects: If
!f
,*this
has no target; otherwise, move-constructs the target off
into the target of*this
, leavingf
in a valid state with an unspecified value. If an allocator is not specified, the constructed function will use the same allocator asf
.template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);-7- Requires:
-8- Remarks: These constructors shall not participate in overload resolution unlessF
shall beCopyConstructible
.f
is Callable (20.9.11.2) for argument typesArgTypes...
and return typeR
.-9- Postconditions:
!*this
if any of the following hold:
f
is a null function pointer value.
f
is a null member pointer value.
F
is an instance of the function class template, and!f
-10- Otherwise,
*this
targets a copy off
initialized withstd::move(f)
. [Note: Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, wheref
's target is an object holding only a pointer or reference to an object and a member function pointer. — end note]-11- Throws: shall not throw exceptions when an allocator is not specified and
f
is a function pointer or areference_wrapper<T>
for someT
. Otherwise, may throwbad_alloc
or any exception thrown byF
's copy or move constructor or byA
's allocate function.
[2016-08 Chicago]
Tues PM: Resolved by P0302R1
Proposed resolution:
Resolved by acceptance of P0302R1.