Visible to Intel only — GUID: GUID-E13AF445-8A26-4320-8594-A99CC4611A91
Visible to Intel only — GUID: GUID-E13AF445-8A26-4320-8594-A99CC4611A91
Invoke a Callable Object
Starting from C++17, the requirements for callable objects passed to algorithms or Flow Graph nodes are relaxed. It allows using additional types of bodies. Previously, the body of the algorithm or Flow Graph node needed to be a Function Object (see C++ Standard Function Object) and provide an operator() that accepts input parameters.
Now the body needs to meet the more relaxed requirements of being Callable (see C++ Standard Callable) that covers three types of objects:
Function Objects that provide operator(arg1, arg2, …), which accepts the input parameters
Pointers to member functions that you can use as the body of the algorithm or the Flow Graph node
Pointers to member objects work as the body of the algorithm or parallel construct
You can use it not only for a Flow Graph but also for algorithms. See the example below:
// The class models oneTBB Range class StrideRange { public: StrideRange(int* s, std::size_t sz, std::size_t str) : start(s), size(sz), stride(str) {} // A copy constructor StrideRange(const StrideRange&) = default; // A splitting constructor StrideRange(StrideRange& other, oneapi::tbb::split) : start(other.start), size(other.size / 2) { other.size -= size; other.start += size; } ~StrideRange() = default; // Indicate if the range is empty bool empty() const { return size == 0; } // Indicate if the range can be divided bool is_divisible() const { return size >= stride; } void iterate() const { for (std::size_t i = 0; i < size; i += stride) { // Performed an action for each element of the range, // implement the code based on your requirements } } private: int* start; std::size_t size; std::size_t stride; };
Where:
The StrideRange class models oneTBB range that should be iterated with a specified stride during its initial construction.
The stride value is stored in a private field within the range. Therefore, the class provides the member function iterate() const that implements a loop with the specified stride.
range.iterate()
Before C++17, to utilize a range in a parallel algorithm, such as parallel_for, it was required to provide a Function Object as the algorithm’s body. This Function Object defined the operations to be executed on each iteration of the range:
int main() { std::size_t array_size = 1000; int* array_to_iterate = new int[array_size]; StrideRange range(array_to_iterate, array_size, /* stride = */ 2); // Define a lambda function as the body of the parallel_for loop auto pfor_body = [] (const StrideRange& range) { range.iterate(); }; // Perform parallel iteration oneapi::tbb::parallel_for(range, pfor_body); delete[] array_to_iterate; }
An additional lambda function pfor_body was also required. This lambda function invoked the rage.iterate() function.
Now with C++17, you can directly utilize a pointer to range.iterate() as the body of the algorithm:
int main() { std::size_t array_size = 1000; int* array_to_iterate = new int[array_size]; // Performs the iteration over the array elements with the specified stride StrideRange range(array_to_iterate, array_size, /* stride = */ 2); // Parallelize the iteration over the range object oneapi::tbb::parallel_for(range, &StrideRange::iterate); delete[] array_to_iterate; }
std::invoke
std::invoke is a function template that provides a syntax for invoking different types of callable objects with a set of arguments.
oneTBB implementation uses the C++ standard function std::invoke(&StrideRange::iterate, range) to execute the body. It is the equivalent of range.iterate(). Therefore, it allows you to invoke a callable object, such as a function object, with the provided arguments.
Example
Consider a specific scenario with function_node within a Flow Graph.
In the example below, a function_node takes an object as an input to read a member object of that input and proceed it to the next node in the graph:
struct Object { int number; }; int main() { using namespace oneapi::tbb::flow; // Lambda function to read the member object of the input Object auto number_reader = [] (const Object& obj) { return obj.number; }; // Lambda function to process the received integer auto number_processor = [] (int i) { /* processing integer */ }; graph g; // Function node that takes an Object as input and produces an integer function_node<Object, int> func1(g, unlimited, number_reader); // Function node that takes an integer as input and processes it function_node<int, int> func2(g, unlimited, number_processor); // Connect the function nodes make_edge(func1, func2); // Provide produced input to the graph func1.try_put(Object{1}); // Wait for the graph to complete g.wait_for_all(); }
Before C++17, the function_node in the Flow Graph required the body to be a Function Object. A lambda function was required to extract the number from the Object.
With C++17, you can use std::invoke with a pointer to the member number directly as the body.
You can update the previous example as follows:
struct Object { int number; }; int main() { using namespace oneapi::tbb::flow; // The processing logic for the received integer auto number_processor = [] (int i) { /* processing integer */ }; // Create a graph object g to hold the flow graph graph g; // Use a member function pointer to the number member of the Object struct as the body function_node<Object, int> func1(g, unlimited, &Object::number); // Use the number_processor lambda function as the body function_node<int, int> func2(g, unlimited, number_processor); // Connect the function nodes make_edge(func1, func2); // Connect the function nodes func1.try_put(Object{1}); // Wait for the graph to complete g.wait_for_all(); }
Find More
The following APIs supports Callable object as Bodies: