Quick Links: |
Provide a polymorphic function object with a specific prototype. More...
bsl::function | polymorphic function object with a specific prototype. |
bsl::function
, implementing the standard template std::function
, a runtime-polymorphic wrapper that encapsulates an arbitrary callable object (the target) and allows the wrapped object to be invoked. bsl::function
extends std::function
by adding allocator support in a manner consistent with standards proposal P0987 (http://wg21.link/P0987). bsl::function
generalize the notion of function pointers and are generally used to pass callbacks to a non-template function or class. For example, bsl::function<RET (ARG1, ARG2, ...)>
can be used similarly to RET (*)(ARG1, ARG2, ...)
but, unlike the function pointer, the bsl::function
can hold a non-function callable type such as pointer to member function, pointer to member data, lambda expression, or functor (class type having an operator()
). A bsl::function
can also be "empty", i.e., having no target object. In a bool
context, a bsl::function
object will evaluate to false if it is empty, and true otherwise. The target type is determined at runtime using type erasure in the constructors and can be changed by means of assignment, but the function prototype (argument types and return type) is specified as a template parameter at compile time. bsl::function
is an in-core value-semantic type whose salient attributes are the type and value of its target, if any. The bsl::function
owns the target object and manages its lifetime; copying or moving the bsl::function
object copies or moves the target and destroying the bsl::function
destroys the target. Somewhat counter-intuitively, the target is always mutable within the bsl::function
; when wrapping a class type, calling a bsl::function
can modify its target object, even if the bsl::function
itself is const-qualified. bsl::function
does have an abstract notion of "value", there is no general equality operator comparing between two bsl::function
objects. This limitation is a consequence of the target type not being required to provide equality comparison operators. The operator==
overloads that are provided compare a bsl::function
against the null pointer and do not satisfy the requirements we typically expect for value-semantic equality operators. bsl::function
object will cause it to throw a bsl::bad_function_call
exception. Given a non-empty object of type bsl::function<RET(ARG0, ARG1, ...)>
invoked with arguments arg0
, arg1
, ..., invocation of the target follows the definition of INVOKE in section [func.require] of the C++ standard. These rules are summarized in the following table: +----------------------------+-----------------------+
| Type of target object, 'f' | Invocation expression |
+============================+=======================+
| Functor, function, or | f(arg0, arg1, ...) |
| pointer to function | |
+----------------------------+-----------------------+
| Pointer to member function | (arg0X.*f)(arg1, ...) |
+----------------------------+-----------------------+
| Pointer to member data | arg0X.*f |
+----------------------------+-----------------------+
f
must be implicitly convertible from the corresponding argument types ARG0
, ARG1
, ... and the return value of the call expression must be implicitly convertible to RET
, unless RET
is void
. R (T::*f)(...)
, or pointer to data member R T::*f
, arg0X
is one of the following: arg0
if ARG0
is T
or derived from T
arg0.get()
if ARG0
is a specialization of reference_wrapper
(*arg0)
if ARG0
is a pointer type or pointer-like type (e.g., a smart pointer). operator()
. std::function
. This specification was never implemented by any vendor or popular open-source standard library and allocator support was removed from the 2017 standard version of std::function
. A new design for allocator support using std::pmr::polymorphic_allocator
instead of type erasure is currently part of version 3 of the Library Fundamentals Technical Specification (LFTS 3), after acceptance of paper P0987 (http://wg21.link/P0987). This component follows the P0987 specification, substituting bsl::allocator
for std::pmr::polymorphic_allocator
. bsl::function
meets the requirements for an allocator-aware type. Specifically: allocator_type
is an alias for bsl::allocator<char>
, bsl::allocator_arg_t
leading-allocator argument convention. get_allocator()
returns the allocator specified at construction. bsl::function
: bsl::function
class has a buffer capable of holding a small callable object without allocating dynamic memory. The buffer is guaranteed to be large enough to hold a pointer to function, pointer to member function, pointer to member data, a bsl::reference_wrapper
, or a stateless functor. In practice, it is large enough to hold many stateful functors up to six times the size of a void *
. Note that, even if the target object is stored in the small object buffer, memory might still be allocated by the target object itself. bsl::function
will store the target object in allocated memory: bsl::function
to be noexcept
, as required by the C++ Standard. bsl::function
object, then assign it to callable objects of different types at run time. int intXor(int a, int b) { return a ^ b; }
bsl::function
that takes two integers and returns an integer. Because we have not initialized the object with a target, it starts out as empty and evaluates to false in a Boolean context: void main() { bsl::function<int(int, int)> funcObject; assert(! funcObject);
intXor
and test that we can invoke it to get the expected result: funcObject = intXor; assert(funcObject); assert(5 == funcObject(6, 3));
std::plus<int>
functor to funcObject
, which then holds a copy of it, and again test that we get the expected result when we invoke funcObject
. funcObject = std::plus<int>(); assert(funcObject); assert(9 == funcObject(6, 3));
#if BSLS_COMPILERFEATURES_CPLUSPLUS >= 201103L funcObject = [](int a, int b) { return a * b; }; assert(funcObject); assert(18 == funcObject(6, 3)); #endif
funcObject
to nullptr
, which makes it empty again: funcObject = bsl::nullptr_t(); assert(! funcObject); }
int *
), the type of the transformation operation can be anything that can be called with an integral argument and produces an integral return value. We do not want to accept this operation as a template argument, however (perhaps because our algorithm is sufficiently complex and/or proprietary that we want to keep it out of header files). We solve these disparate requirements by passing the operation as a bsl::function
object, whose type is known at compile time but which can be set to an arbitrary operation at run time: void myAlgorithm(const int *begin, const int *end, int *output, const bsl::function<int(int)>& op); // Apply my special algorithm to the elements in the contiguous address // range from the specified 'begin' pointer up to but not including the // specified 'end' pointer, writing the result to the contiguous range // starting at the specified 'output' pointer. The specified 'op' // function is applied to each element before it is fed into the // algorithm.
myAlgorithm
is a simple loop that invokes the specified op
on each element in the input range and writes it directly to the output: void myAlgorithm(const int *begin, const int *end, int *output, const bsl::function<int(int)>& op) { for (; begin != end; ++begin) { *output++ = op(*begin); } }
static const std::size_t DATA_SIZE = 5; static const int testInput[DATA_SIZE] = { 4, 3, -2, 9, -7 }; static int testOutput[DATA_SIZE];
long negate(long v) { return -v; } // Return the arithmetic negation of the specified 'v' integer.
bool testNegation() // Test the use of the 'negation' function with 'myAlgorithm'. { myAlgorithm(testInput, testInput + DATA_SIZE, testOutput, negate); for (std::size_t i = 0; i < DATA_SIZE; ++i) { if (-testInput[i] != testOutput[i]) { return false; // RETURN } } return true; }
negate
is not identical to the prototype used to instantiate the op
argument in myAlgorithm
. All that is required is that each argument to op
be convertible to the corresponding argument in the function and that the return type of the function be convertible to the return type of op
. class RunningSum { // Keep a running total of all of the inputs provided to 'operator()'. // DATA int d_sum; public: // CREATORS explicit RunningSum(int initial = 0) : d_sum(initial) { } // Create a 'RunningSum' with initial value set to the specified // 'initial' argument. // MANIPULATORS int operator()(int v) // Add the specified 'v' to the running sum and return the running // sum. { return d_sum += v; } };
myAlgorithm
with RunningSum
: bool testRunningSum() // Test the user of 'RunningSum' with 'myAlgorithm'. { myAlgorithm(testInput, testInput+DATA_SIZE, testOutput, RunningSum()); int sum = 0; for (std::size_t i = 0; i < DATA_SIZE; ++i) { sum += testInput[i]; if (sum != testOutput[i]) { return false; // RETURN } } return true; }
RunningSum::operator()
is a mutating operation and that, within myAlgorithm
, op
is const. Even though bsl::function
owns a copy of its target, logical constness does not apply, as per the standard. void main()
{
assert(testNegation());
assert(testRunningSum());
}
bsl::function
taking a WorkQueue
pointer argument and returning void
. class WorkQueue; // Forward declaration typedef bsl::function<void(WorkQueue *)> WorkItem;
class WorkQueue { // A FIFO queue of tasks to be executed. // PRIVATE CONSTANTS static const int k_MAX_ITEMS = 16; // DATA int d_numItems; int d_head; WorkItem d_items[k_MAX_ITEMS]; public: // CREATORS WorkQueue() // Create an empty work queue. : d_numItems(0), d_head(0) { } // MANIPULATORS void dequeue(WorkItem *result) // Move the work item at the head of the queue into the specified // 'result' and remove it from the queue. The behavior is // undefined if this queue is empty. { assert(d_numItems > 0); *result = bslmf::MovableRefUtil::move(d_items[d_head]); d_head = (d_head + 1) % k_MAX_ITEMS; // circular --d_numItems; } void enqueue(bslmf::MovableRef<WorkItem> item) // Enqueue the specified 'item' work item onto the tail of the // queue. The work is moved from 'item'. { int tail = (d_head + d_numItems++) % k_MAX_ITEMS; // circular assert(d_numItems <= k_MAX_ITEMS); d_items[tail] = bslmf::MovableRefUtil::move(item); } // ACCESSORS bool isEmpty() const // Return true if there are no items in the queue; otherwise return // false. { return 0 == d_numItems; } int size() const // Return the number of items currently in the queue. { return d_numItems; } };
class Worker { // A simulated worker thread. // DATA bool d_isIdle; // True if the worker is idle public: // CREATORS Worker() // Create an idle worker. : d_isIdle(true) { } // MANIPULATORS void run(WorkQueue *queue); // Dequeue a task from the specified 'queue' and execute it // (asynchronously, in theory). The behavior is undefined unless // this worker is idle before the call to 'run'. // ACCESSORS bool isIdle() const // Return whether this worker is idle. An idle worker is one that // can except work. { return d_isIdle; } };
run
function, which removes a bsl::function
object from the work queue and then executes it, passing the work queue as the sole argument: void Worker::run(WorkQueue *queue) { if (queue->isEmpty()) { // No work to do return; // RETURN } WorkItem task; queue->dequeue(&task); d_isIdle = false; // We're about to do work. task(queue); // Do the work. d_isIdle = true; // We're idle again. }
class Scheduler { // Parallel work scheduler. // PRIVATE CONSTANTS static const int k_NUM_WORKERS = 4; // DATA WorkQueue d_workQueue; Worker d_workers[k_NUM_WORKERS]; public: // CREATORS explicit Scheduler(bslmf::MovableRef<WorkItem> initialTask) // Create a scheduler and enqueue the specified 'initialTask'. { d_workQueue.enqueue(bslmf::MovableRefUtil::move(initialTask)); } // MANIPULATORS void run(); // Execute the tasks in the work queue (theoretically in parallel) // until the queue is empty. };
run
method: which does a round-robin scheduling of the workers, allowing each to pull work off of the queue and run it. As tasks are run, they may enqueue more work. The scheduler returns when there are no more tasks in the queue. void Scheduler::run() { while (! d_workQueue.isEmpty()) { for (int i = 0; i < k_NUM_WORKERS; ++i) { if (d_workers[i].isIdle()) { d_workers[i].run(&d_workQueue); } } } }
template <class TYPE> class QuickSortTask { // A functor class to execute parallel quicksort on a contiguous range // of elements of specified 'TYPE' supplied at construction. // DATA TYPE *d_begin_p; TYPE *d_end_p; // PRIVATE CLASS METHODS static TYPE* partition(TYPE *begin, TYPE *end); // Partition the contiguous range specified by '[begin, end)' and // return an iterator, 'mid', such that every element in the range // '[begin, mid)' is less than '*mid' and every element in the // range '[mid + 1, end)' is not less than '*mid'. The behavior is // undefined unless 'begin < end'. public: // CREATORS QuickSortTask(TYPE *begin, TYPE *end) // Create a task to sort the contiguous range from the item at the // specified 'begin' location up to but not included the item at // the specified 'end' location. : d_begin_p(begin), d_end_p(end) { } // MANIPULATORS void operator()(WorkQueue *queue); // Preform the sort in parallel using the specified 'queue' to // enqueue parallel work. };
partition
method, using a variation of the Lomuto partition scheme: template <class TYPE> TYPE* QuickSortTask<TYPE>::partition(TYPE *begin, TYPE *end) { using std::swap; swap(begin[(end - begin) / 2], end[-1]); // Put pivot at end TYPE& pivot = *--end; TYPE *divider = begin; for (; begin != end; ++begin) { if (*begin < pivot) { swap(*divider, *begin); ++divider; } } swap(*divider, pivot); // Put pivot in the middle return divider; }
template <class TYPE> void QuickSortTask<TYPE>::operator()(WorkQueue *queue) { if (d_end_p - d_begin_p < 2) { // Zero or one element. End recursion. return; // RETURN } // Partition returns end iterator for low partition == begin iterator // for high partition. TYPE *mid = partition(d_begin_p, d_end_p); // Asynchronously sort the two partitions WorkItem sortLoPart(QuickSortTask(d_begin_p, mid)); WorkItem sortHiPart(QuickSortTask(mid + 1, d_end_p)); queue->enqueue(bslmf::MovableRefUtil::move(sortLoPart)); queue->enqueue(bslmf::MovableRefUtil::move(sortHiPart)); }
QuickSortTask
to sort an array initially containing the integers between 1 and 31 in random order: void main() { short data[] = { 23, 12, 2, 28, 1, 10, 5, 13, 15, 8, 19, 14, 31, 29, 9, 11, 24, 3, 30, 7, 17, 27, 20, 21, 18, 4, 22, 25, 16, 6, 26 }; static const int DATA_SIZE = sizeof(data) / sizeof(data[0]); WorkItem initialTask(QuickSortTask<short>(data, data + DATA_SIZE)); Scheduler sched(bslmf::MovableRefUtil::move(initialTask)); sched.run(); // Validate results for (int i = 0; i < DATA_SIZE; ++i) { assert(i + 1 == data[i]); } }