Callback keyword and Pass a function as an argument in C++

2024-09-10 , 4 mins read.

We will walk through the concepts of callbacks and passing functions as arguments in C++.

1. What is a "Callback"?

A callback is a function that you pass as an argument to another function, and that function can call (or "invoke") the passed-in function at some point. It's like saying, "I'll pass you this function, and you call it when you need it."

In C++, callbacks are usually implemented using function pointers or, more commonly in modern C++, using std::function and lambdas (anonymous functions).

2. How to Pass a Function as an Argument in C++?

In C++, you can pass functions as arguments using the following techniques:

  • Function Pointers (classic C++ approach)

  • std::function (modern C++ approach, introduced in C++11)

  • Lambdas (anonymous inline functions, introduced in C++11)

Example of Using Function Pointers:

If you have a function myFunction that you want to pass to another function, you can use function pointers.

// Define a function
void myFunction(int x) {
    std::cout << "Called with: " << x << std::endl;
}

// Function that takes a function pointer as an argument
void callWithArgument(void (*callback)(int), int value) {
    // Call the function passed as the callback
    callback(value);
}

int main() {
    // Pass myFunction as a function pointer
    callWithArgument(myFunction, 42);
}

Output:

Called with: 42

Example of Using std::function and Lambdas:

In modern C++, std::function is used to pass functions as arguments, and lambdas are inline anonymous functions that can be passed to std::function.

Here’s an example using both:

#include <iostream>
#include <functional> // For std::function

// A function that takes a std::function (callback) as an argument
void callWithCallback(std::function<void(int)> callback, int value) {
    // Call the function passed as the callback
    callback(value);
}

int main() {
    // Lambda function that can be passed as a callback
    auto lambda = [](int x) {
        std::cout << "Lambda called with: " << x << std::endl;
    };

    // Call with lambda as the callback
    callWithCallback(lambda, 42);

    // You can also pass a regular function
    callWithCallback([](int x) { std::cout << "Inline Lambda called with: " << x << std::endl; }, 10);
}

Output:

Lambda called with: 42
Inline Lambda called with: 10

3. How to Use a Function as an Argument in C++?

When you want to pass a function as an argument, the function you pass could either be:

  • A regular function

  • A lambda function

  • A function pointer

  • A std::function type, which can hold a variety of callable objects (including lambdas, function pointers, and more)

In KVDB project, we use std::function<void(const KeyValue&)> in the inOrderTraversal function, passing a function (or lambda) that takes a KeyValue and returns void.

Here's an example using std::function in a tree traversal:

Using std::function in Tree Traversal

#include <functional>  // For std::function
#include <iostream>

// Simplified KeyValue structure
struct KeyValue {
    int key;
    int value;
    
    KeyValue(int k, int v) : key(k), value(v) {}
    
    bool operator>=(const KeyValue& other) const { return key >= other.key; }
    bool operator<=(const KeyValue& other) const { return key <= other.key; }
    ...
    ...
};

// Simplified RedBlackTree class with an inOrderTraversal method
class RedBlackTree {
public:
    struct TreeNode {
        KeyValue keyValue;
        TreeNode* left;
        TreeNode* right;

        TreeNode(KeyValue kv) : keyValue(kv), left(nullptr), right(nullptr) {}
    };

    RedBlackTree() : root(nullptr) {}

    // Insert for demonstration (simplified, non-balanced insertion)
    void insert(KeyValue kv) {
        root = insertRec(root, kv);
    }

    // Inorder traversal with callback (using std::function)
    void inOrderTraversal(std::function<void(const KeyValue&)> callback) const {
        inOrderTraversal(root, callback);
    }

private:
    TreeNode* root;

    TreeNode* insertRec(TreeNode* node, KeyValue kv) {
        if (!node) return new TreeNode(kv);
        if (kv.key < node->keyValue.key) node->left = insertRec(node->left, kv);
        else node->right = insertRec(node->right, kv);
        return node;
    }

    void inOrderTraversal(TreeNode* node, std::function<void(const KeyValue&)> callback) const {
        if (node == nullptr) return;
        inOrderTraversal(node->left, callback);  // Visit left subtree
        callback(node->keyValue);                // Apply callback to current node
        inOrderTraversal(node->right, callback); // Visit right subtree
    }
};

int main() {
    RedBlackTree tree;
    tree.insert(KeyValue(10, 100));
    tree.insert(KeyValue(5, 50));
    tree.insert(KeyValue(20, 200));

    // Use inOrderTraversal with a lambda callback
    tree.inOrderTraversal([](const KeyValue& kv) {
        std::cout << "Key: " << kv.key << ", Value: " << kv.value << std::endl;
    });

    return 0;
}

Output:

Key: 5, Value: 50
Key: 10, Value: 100
Key: 20, Value: 200

Explanation:

  1. Lambda Functions: The inOrderTraversal function accepts a lambda as a callback. This lambda processes each key-value pair during the traversal.

  2. std::function: std::function<void(const KeyValue&)> is used to define the type of the callback function, which takes a KeyValue as an argument and returns void.

Summary:

  • Callback refers to a function passed as an argument to another function, which is called ("invoked") within the receiving function.

  • You can pass functions as arguments using function pointers or std::function (the modern approach).

  • Lambdas are a convenient way to define inline, anonymous functions and pass them as arguments.

Last updated