Do you know the functions Apply and Invoke? Those two methods from “std” are linked to the concept of “callable” objects.
A “callable” object definition is simple. It could be a function, or a class object which overloads “operator()” (which can be a method!).
The purpose of “Apply” and “Invoke”
One of the goals of those functions is to invoke an object directly inside a function. But, why using Invoke and Apply when I can simply call “myFunction()” or “myObject.myMethod()”?
You could, for instance, switch a simple pointer to change the callable object or the “class object” call! Moreover, apply has a great feature that can help you to do cleaner code!
During this post, I will show you different implementations of Apply and Invoke, their differences, and purpose in detail.
So, here we go!
To feature the different usages, I will use the following class:
class Rectangle { public: Rectangle(const unsigned int height, const unsigned int width, const std::string rectangleName = "Rectangle"); virtual ~Rectangle() {}; unsigned long getArea() const; static unsigned long getAreaStatic(const Rectangle& rectangle); void changeConfiguration(const unsigned int height, const unsigned int width, const std::string rectangleName = "Rectangle"); private: unsigned int height; unsigned int width; std::string rectangleName; }; int multiplicationTwoArgs(short a = 1, short b = 1) { return (a*b); } int multiplicationThreeArgs(short a = 1, short b = 1, short c = 1) { return (a*b*c); }
How to use Invoke
For “invoke”, you should include the header “functional”:
#include <functional>
The usage of “invoke” is pretty simple.
At first, you should include the callable object. It could be a pointer to a method, a function, or a static function.
After this first parameter, you put the necessary arguments (like a simple call!). For instance, the call of “myFunction(int a, int b)”, will be converted to “std::invoke (myFunction, a, b).”
Then, we can exploit invoke in the following conditions:
- Call a function/Static function
- Call a method/Static method
- Call with Lambda expressions
- Unfortunately, the “default argument values” are not supported (see example below).
For a better comprehension of invoke, let’s see this example:
void invokeUsage() { Rectangle mySquare(2, 2, "Square"); // Invoke from class std::cout << "====== DATA MEMBER INVOKE ======" << std::endl; std::cout << "My Square area = " << std::invoke(&Rectangle::getArea, mySquare) << std::endl; std::cout << "====== STATIC MEMBER INVOKE ======" << std::endl; std::cout << "My Square area = " << std::invoke(&Rectangle::getAreaStatic, mySquare) << std::endl; std::cout << "====== MULTIPLE ARGUMENTS INVOKE ======" << std::endl; std::invoke(&Rectangle::changeConfiguration, mySquare, 3, 4, "Rectangle"); // Invoke with function + arguments std::cout << "====== FUNCTION INVOKE ======" << std::endl; std::cout << std::invoke(multiplicationThreeArgs, 1, 2, 3) << std::endl; // std::invoke(&Rectangle::changeConfiguration, mySquare, 8, 4); <===== Invalid! // Lambda invoke std::cout << "====== LAMBDA INVOKE ======" << std::endl; std::invoke([]() { std::cout << "From lambda!" << std::endl; }); }
Now, let’s build this with the option c++17:
g++ --std=c++17 InvokeAndApply.cpp
And here we go!
./a.out Square created of: height = 2 width = 2 ====== DATA MEMBER INVOKE ====== My Square area = 4 ====== STATIC MEMBER INVOKE ====== My Square area = 4 ====== MULTIPLE ARGUMENTS INVOKE ====== Changing name Square to Rectangle New size: height = 3, width = 4 ====== FUNCTION INVOKE ====== 6 ====== LAMBDA INVOKE ====== From lambda!
How to use Apply
As “invoke”, “apply” requires the header “functional”:
#include <functional>
Apply is one of my favorites features from C++17. Its functionality and behavior are similar to invoke. Excepted that “apply” arguments are stored in a pair, an array, or a tuple! I cannot count the number of times I wanted a function like this in C++!
This kind of inputs is ideal to structure your data and simplify readability. Let’s see an example with apply:
void applyUsage() { std::tuple myTuple(1, 2, 3); std::array myArray = {2, 2, 2}; std::pair myPair(1, 2); // Usage of tuple std::cout << std::apply(multiplicationThreeArgs, myTuple) << std::endl; // Usage of array std::cout << std::apply(multiplicationThreeArgs, myArray) << std::endl; // Usage of pair // std::apply(multiplicationThreeArgs, myPair); <== Invalid! std::cout << std::apply(multiplicationTwoArgs, myPair) << std::endl; }
and let’s run:
./a.out 6 8 2
Of course, you can also call methods from a class:
void applyUsageWithClass() { Rectangle mySquare(2, 2, "Square"); std::tuple myTuple(mySquare, 16, 9, "Golden Rectangle"); // Usage of tuple with class std::apply(&Rectangle::changeConfiguration, myTuple); }
And here is the output:
./a.out Square created of: height = 2 width = 2 Changing name Square to Golden Rectangle New size: height = 16, width = 9
Let's summarize...
Invoke
Apply
Conclusion
As you saw during this post, Invoke and Apply are similar in many points. The only difference concerns the input configuration.
For "Invoke", inputs are directly exploited. If a tuple or array is put as a parameter, Invoke will not unpack this.
Concerning "Apply", only two parameters can enter. However, the second argument is an unpacked Tuple.