How to Use the Standard Template Library (STL) in C++

In this tutorial, we will learn how to use the Standard Template Library (STL) in C++.

Prerequisites

Working with Double-Ended Queues (Deques)

Let’s explore how to use deques in C++ for managing sensor data streams and other robotics applications. Deques are particularly useful when you need to process data from both ends of a collection efficiently. 

Open a terminal window, and type this:

cd ~/Documents/cpp_tutorial 
code . 

Now, let’s create a new C++ file and name it sensor_data_handling.cpp.

Type the following code into the editor:

#include <iostream>
#include <deque>

int main() {
    // Simulating a stream of sensor data
    std::deque<float> sensor_data;

    // Adding new readings at the back
    sensor_data.push_back(2.5);
    sensor_data.push_back(3.1);
    sensor_data.push_back(4.7);

    // Processing new reading at the front
    std::cout << "Processing sensor reading: " << sensor_data.front() << std::endl;
    sensor_data.pop_front();

    // More readings are added
    sensor_data.push_back(5.5);
    sensor_data.push_back(6.8);

    // Processing another reading
    std::cout << "Processing sensor reading: " << sensor_data.front() << std::endl;
    sensor_data.pop_front();

    // Display remaining data
    std::cout << "Remaining sensor data:";
    for (float reading : sensor_data) {
        std::cout << ' ' << reading;
    }
    std::cout << std::endl;

    return 0;
}

Run the code.

1-sensor-handling

You should see how the sensor readings are processed and the status of the queue after each operation.

This example demonstrates how deques can be effectively used in robotics to handle data streams where the newest data might need immediate processing and older data needs to be cleared after handling. This is important for maintaining real-time performance in systems like autonomous vehicles or robotic sensors.

Employing Iterators

Let’s explore how to employ iterators in C++ for robotics applications. Iterators provide a flexible way to traverse and manipulate elements in containers, such as vectors and arrays, which are commonly used in robotics to store and process data.

Let’s start by creating a new C++ file and naming it iterator_example.cpp.

Type the following code into the editor:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> sensor_data = {10, 20, 30, 40, 50};
    
    // Using iterators to traverse and print the vector
    for (auto it = sensor_data.begin(); it != sensor_data.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    // Using iterators to modify elements in the vector
    for (auto it = sensor_data.begin(); it != sensor_data.end(); ++it) {
        *it *= 2;
    }
    
    // Printing the modified vector
    for (const auto& value : sensor_data) {
        std::cout << value << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

In this code, we include the necessary headers: iostream for input/output operations and vector for the vector container.

In the main function, we create a vector named sensor_data to represent a collection of sensor readings. We initialize it with some sample values.

We use iterators to traverse and print the elements of the vector. We create an iterator it and initialize it to sensor_data.begin(), which points to the first element. 

We iterate until it reaches sensor_data.end(), which is the position after the last element. Inside the loop, we dereference the iterator using *it to access the value at the current position and print it.

Next, we use iterators to modify the elements in the vector. We create another iterator it and iterate over the vector as before. This time, we dereference the iterator and multiply the value by 2, effectively doubling each element.

Finally, we print the modified vector using a range-based for loop, which automatically uses iterators under the hood to traverse the vector.

Run the code.

2-iterator-example

You should see the original vector printed, followed by the modified vector with each element doubled.

Working with Deques, Lists, and Forward lists

Let’s explore how to work with deques, lists, and forward lists in C++ for robotics applications. These container types offer different characteristics and are useful in various scenarios when dealing with robotic data and algorithms.

Let’s start by creating a new C++ file and naming it container_example.cpp.

Type the following code into the editor:

#include <iostream>
#include <deque>
#include <list>
#include <forward_list>

int main() {
    // Working with deques
    std::deque<int> robot_positions = {10, 20, 30};
    robot_positions.push_front(5);
    robot_positions.push_back(40);
    std::cout << "Deque: ";
    for (const auto& pos : robot_positions) {
        std::cout << pos << " ";
    }
    std::cout << std::endl;

    // Working with lists
    std::list<std::string> robot_actions = {"move", "rotate", "scan"};
    robot_actions.push_back("grasp");
    robot_actions.push_front("initialize");
    std::cout << "List: ";
    for (const auto& action : robot_actions) {
        std::cout << action << " ";
    }
    std::cout << std::endl;

    // Working with forward lists
    std::forward_list<double> sensor_readings = {1.5, 2.7, 3.2};
    sensor_readings.push_front(0.8);
    std::cout << "Forward List: ";
    for (const auto& reading : sensor_readings) {
        std::cout << reading << " ";
    }
    std::cout << std::endl;

    return 0;
}

In this code, we include the necessary headers: iostream for input/output operations, deque for the deque container, list for the list container, and forward_list for the forward list container.

In the main function, we demonstrate working with each container type:

  • Deque: We create a deque named robot_positions to store integer positions. We use push_front() to add an element at the front and push_back() to add an element at the back. We then print the contents of the deque using a range-based for loop.
  • List: We create a list named robot_actions to store string actions. We use push_back() to add an element at the back and push_front() to add an element at the front. We print the contents of the list using a range-based for loop.
  • Forward List: We create a forward list named sensor_readings to store double readings. We use push_front() to add an element at the front. We print the contents of the forward list using a range-based for loop.

Run the code.

3-container-example

You will see the contents of each container printed in the terminal.

Deques allow efficient insertion and deletion at both ends, lists provide constant-time insertion and deletion anywhere in the container, and forward lists offer a singly-linked list with efficient insertion and deletion at the front.

Handling Sets and Multisets

Let’s explore how to handle sets and multisets in C++ for robotics applications. Sets and multisets are associative containers that store unique and duplicate elements, respectively, and they can be useful for managing distinct or repeated data in robotic systems.

Let’s start by creating a new C++ file and naming it set_example.cpp.

Type the following code into the editor: 

#include <iostream>
#include <set>

int main() {
    // Handling sets
    std::set<int> unique_landmarks = {10, 20, 30, 20, 40, 30};
    std::cout << "Unique Landmarks: ";
    for (const auto& landmark : unique_landmarks) {
        std::cout << landmark << " ";
    }
    std::cout << std::endl;

    // Handling multisets
    std::multiset<std::string> repeated_commands = {"move", "rotate", "scan", "move", "grasp"};
    std::cout << "Repeated Commands: ";
    for (const auto& command : repeated_commands) {
        std::cout << command << " ";
    }
    std::cout << std::endl;

    return 0;
}

In this code, we include the necessary headers: iostream for input/output operations and set for the set and multiset containers.

In the main function, we demonstrate handling sets and multisets:

  • Set: We create a set named unique_landmarks to store unique integer landmarks. We initialize it with some values, including duplicates. The set automatically removes the duplicate elements and stores only the unique values. We print the contents of the set using a range-based for loop.
  • Multiset: We create a multiset named repeated_commands to store repeated string commands. We initialize it with some values, including duplicates. The multiset allows duplicate elements and stores all the occurrences. We print the contents of the multiset using a range-based for loop.

Run the code.

4-set-example

You will see the unique landmarks printed from the set and the repeated commands printed from the multiset.

Sets are useful when you need to store and efficiently retrieve unique elements, such as distinct landmarks or sensor readings. Multisets, on the other hand, allow you to store and manage duplicate elements, which can be helpful for tracking repeated commands or measurements in robotic systems.

Using Map and Multimaps

Let’s explore how to use map and multimap in C++ for robotics applications. Map and multimap are associative containers that store key-value pairs, allowing efficient lookup and retrieval of values based on their associated keys.

Let’s start by creating a new C++ file and naming it map_example.cpp.

Type the following code into the editor:

#include <iostream>
#include <map>
#include <string>

int main() {
    // Using map
    std::map<std::string, int> sensor_readings;
    sensor_readings["temperature"] = 25;
    sensor_readings["humidity"] = 60;
    sensor_readings["pressure"] = 1013;

    for (const auto& reading : sensor_readings) {
        std::cout << reading.first << ": " << reading.second << std::endl;
    }

    // Using multimap
    std::multimap<std::string, std::string> robot_commands;
    robot_commands.insert({"move", "forward"});
    robot_commands.insert({"move", "backward"});
    robot_commands.insert({"rotate", "left"});
    robot_commands.insert({"rotate", "right"});

    for (const auto& command : robot_commands) {
        std::cout << command.first << ": " << command.second << std::endl;
    }

    return 0;
}

In this code, we include the necessary headers: iostream for input/output operations, map for the map and multimap containers, and string for string manipulation.

In the main function, we first demonstrate the usage of map. We create a map named sensor_readings that associates sensor names (keys) with their corresponding values. We insert key-value pairs into the map using the [] operator. We then iterate over the map using a range-based for loop and print each key-value pair.

Next, we demonstrate the usage of multimap. We create a multimap named robot_commands that associates command types (keys) with their corresponding parameters (values). We insert key-value pairs into the multimap using the insert() function. 

Multimap allows duplicate keys, so we can have multiple entries with the same command type. We iterate over the multimap using a range-based for loop and print each key-value pair.

Run the code.

5-map-example

You will see the sensor readings and robot commands printed in the terminal, demonstrating the usage of map and multimap.

Map is useful when you need to associate unique keys with their corresponding values, such as storing sensor readings or configuration parameters. 

Multimap allows duplicate keys and is helpful when you need to store multiple values for the same key, such as mapping command types to their parameters.

Manipulating Stack and Queue

Let’s explore how to manipulate stacks and queues in C++, essential data structures for various robotics applications.

Create a new C++ file called stack_queue_example.cpp.

Type the following code into the editor:

#include <iostream>
#include <stack>
#include <queue>

int main() {
    // Stack example
    std::stack<int> my_stack;
    my_stack.push(10);
    my_stack.push(20);
    my_stack.push(30);

    std::cout << "Top element of the stack: " << my_stack.top() << std::endl;
    my_stack.pop(); // Removes the top element (30)

    std::cout << "Updated top element: " << my_stack.top() << std::endl;

    // Queue example
    std::queue<std::string> my_queue;
    my_queue.push("Sensor data");
    my_queue.push("Robot command");
    my_queue.push("Navigation goal");

    std::cout << "Front element of the queue: " << my_queue.front() << std::endl;
    my_queue.pop(); // Removes the front element ("Sensor data")

    std::cout << "Updated front element: " << my_queue.front() << std::endl;

    return 0;
}

First, we create a stack my_stack to store integers. We push the values 10, 20, and 30 onto the stack using the push method. 

We then print the top element of the stack using the top method, which returns 30. 

Next, we remove the top element from the stack using the pop method. 

Finally, we print the updated top element, which is now 20.

For the queue example, we create a queue my_queue to store strings. 

We add the strings “Sensor data”, “Robot command”, and “Navigation goal” using the push method. 

We then print the front element of the queue using the front method, which returns “Sensor data”. 

Next, we dequeue (pronounced as “dee-queue”) the front element using the pop method. 

Finally, we print the updated front element, which is now “Robot command”.

Run the code.

stack

You will see the top and front elements of the stack and queue, respectively, printed in the terminal.

Implementing Priority Queues

Let’s explore how priority queues can be utilized in C++ to manage robotic tasks efficiently. 

Priority queues are particularly useful in robotics for scheduling tasks based on their priority level, ensuring that critical operations like obstacle avoidance or emergency stops are handled first.

Let’s begin by creating a new C++ file named robotic_tasks_priority_queue.cpp.

Type the following code into the editor:

#include <iostream>
#include <queue>
#include <vector>
#include <functional>  // For std::greater

struct Task {
    int priority;
    std::string description;

    // Operator overloading for priority comparison
    bool operator<(const Task& other) const {
        return priority < other.priority;  // Higher numbers mean higher priority
    }
};

int main() {
    // Create a priority queue to manage tasks
    std::priority_queue<Task> tasks;

    // Insert tasks
    tasks.push({2, "Navigate to charging station"});
    tasks.push({1, "Send sensor data"});
    tasks.push({3, "Emergency stop"});

    // Execute tasks based on priority
    while (!tasks.empty()) {
        Task task = tasks.top();
        tasks.pop();
        std::cout << "Executing task: " << task.description << std::endl;
    }

    return 0;
}

In this code, we define a Task struct with a priority and description. 

We overload the < operator to compare tasks based on their priority. 

We then create a priority queue that holds tasks and insert three sample tasks into it. 

We simulate the execution of tasks in order of their priority, with the emergency task taking precedence.

Run the code.

7-priority-queue

You will see the tasks being executed in order of their priority, with the emergency stop being handled first.

Thanks, and I’ll see you in the next tutorial.

Keep building!

How to Use Lambda Expressions and File I/O in C++

In this tutorial, we will explore lambda expressions and file I/O using C++.

Prerequisites

Using Lambda Expressions 

Let’s explore lambda expressions in C++ and how they can be used in robotics projects. 

Lambda expressions allow us to define anonymous functions inline, which is particularly useful for operations like sorting sensor data or defining quick callback functions. 

Open a terminal window, and type this:

cd ~/Documents/cpp_tutorial
code .

Create a new C++ file and name it lambda_sensors.cpp

Type the following code into the editor:

#include <iostream>
#include <vector>
#include <algorithm>  // Include for std::sort

int main() {
    // Example list of sensor readings (could be temperatures, distances, etc.)
    std::vector<int> sensorReadings = {90, 85, 60, 75, 100};

    // Print original readings
    std::cout << "Original readings: ";
    for (int reading : sensorReadings) {
        std::cout << reading << " ";
    }
    std::cout << std::endl;

    // Using a lambda expression to sort the readings in descending order
    std::sort(sensorReadings.begin(), sensorReadings.end(), [](int a, int b) {
        return a > b;  // Comparison criterion defined inline
    });

    // Print sorted readings
    std::cout << "Sorted readings (desc): ";
    for (int reading : sensorReadings) {
        std::cout << reading << " ";
    }
    std::cout << std::endl;

    return 0;
}

In this code, we start with a vector of integers representing sensor readings. The lambda expression is used within the std::sort function. Here, the lambda (int a, int b) { return a > b; } defines an inline function that tells std::sort how to compare two elements, specifying that we want to sort in descending order without the need for a separate function.

Run the code.

1-lambda-sensors

You should see the original and sorted readings displayed, showcasing how lambda expressions facilitate custom sorting in a concise manner.

This example highlights the utility of lambda expressions in C++, allowing for immediate definition of small scope functions directly within the code where they are needed. 

Iterating with for_each Loop

Let’s explore how to use the for_each loop in C++. The for_each loop is part of the C++ Standard Library and provides a clear, concise way to perform operations on every element in a range, such as an array or a container like a vector. This is particularly useful in robotics for tasks like processing sensor data, where you need to apply the same operation to a series of values.

Let’s demonstrate the use of the for_each loop with an example where we’ll process a list of distances recorded by a robot’s sensors.

Create a new C++ file and name it for_each_loop.cpp.

Type the following code into the editor:

#include <iostream>
#include <vector>
#include <algorithm>  // Include for std::for_each

int main() {
    // Example list of distances measured by sensors (in meters)
    std::vector<int> distances = {5, 10, 3, 7, 9};

    // Print original distances
    std::cout << "Original distances: ";
    for (int distance : distances) {
        std::cout << distance << " ";
    }
    std::cout << std::endl;

    // Using a lambda expression with std::for_each to increment each distance by 1
    std::for_each(distances.begin(), distances.end(), [](int &d) {
        d += 1;  // Increment each element by 1
    });

    // Print updated distances
    std::cout << "Updated distances: ";
    for (int distance : distances) {
        std::cout << distance << " ";
    }
    std::cout << std::endl;

    return 0;
}

In this example, we begin with a vector of integers representing distances recorded by a robot. We use std::for_each along with a lambda expression that increments each element in the vector. The lambda (int &d) { d += 1; } directly modifies each element by adding 1, demonstrating an efficient way to process and update data in-place.

Run the code.

2-for-each-loop

You should see both the original and updated distances displayed, showing the practical application of the for_each loop in modifying elements directly.

Handling File Input and output

Let’s explore how to handle file input and output in C++ and its applications in robotics projects.

File input and output are essential operations in many programs, including robotics applications. In C++, the <fstream> library provides classes for reading from and writing to files. The ifstream class is used for reading from files, while the ofstream class is used for writing to files.

Let’s create a new C++ file named file_io_example.cpp to demonstrate file input and output.

Type the following code into the editor:

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::string filename = "robot_data.txt";
    std::ofstream output_file(filename);

    if (output_file.is_open()) {
        output_file << "Sensor1: 10.5\n";
        output_file << "Sensor2: 20.7\n";
        output_file << "Sensor3: 15.2\n";
        output_file.close();
        std::cout << "Data written to file: " << filename << std::endl;
    } else {
        std::cout << "Unable to open file for writing." << std::endl;
    }

    std::ifstream input_file(filename);
    std::string line;

    if (input_file.is_open()) {
        while (std::getline(input_file, line)) {
            std::cout << line << std::endl;
        }
        input_file.close();
    } else {
        std::cout << "Unable to open file for reading." << std::endl;
    }

    return 0;
}

In this code, we first create a file named “robot_data.txt” using an ofstream object. We check if the file is successfully opened using the is_open() function. If the file is open, we write some sensor data to the file using the << operator and then close the file.

Next, we read the data from the same file using an ifstream object. We check if the file is successfully opened for reading. If the file is open, we read the file line by line using getline() and print each line to the console. After reading, we close the file.

Run the code.

3-file-io-example

The output confirms that the data was written to the file and then displays the contents of the file.

In robotics projects, file I/O can be used for various purposes, such as saving sensor data, reading configuration files, or storing robot states for analysis or debugging.

Thanks, and I’ll see you in the next tutorial.

Keep building!

How to Use Structs and Operator Overloading in C++

In this tutorial, we will explore how to use structs and operator overloading in C++. 

Prerequisites

Working with Structs

Let’s explore how to work with structs in C++ and their application in robotics projects. 

Structs can be used in C++ to group related data together, making the code more organized and readable. Structs are particularly useful in robotics projects for representing various data structures, such as robot configurations, sensor readings, or control parameters.

Open a terminal window, and type this:

cd ~/Documents/cpp_tutorial
code .

Create a new C++ file named robot_struct.cpp.

Type the following code into the editor:

#include <iostream>
#include <string>

struct RobotSpec {
    std::string model;
    int max_speed;
    double weight;
};

void print_robot_spec(const RobotSpec& spec) {
    std::cout << "Model: " << spec.model << std::endl;
    std::cout << "Max Speed: " << spec.max_speed << " m/s" << std::endl;
    std::cout << "Weight: " << spec.weight << " kg" << std::endl;
}

int main() {
    RobotSpec robot = {"MobileBot", 5, 10.5};
    print_robot_spec(robot);
    return 0;
}

In this code, we define a struct called RobotSpec to represent the specifications of a robot. It contains three members: model (a string), max_speed (an integer), and weight (a double).

We also define a function print_robot_spec() that takes a constant reference to a RobotSpec object and prints its members.

In the main() function, we create an instance of the RobotSpec struct called robot and initialize its members. 

We then pass robot to the print_robot_spec() function to print its specifications.

Run the code.

1-working-with-structs

The output displays the robot’s specifications as defined in the RobotSpec struct.

Implementing Operator Overloading

Let’s explore the concept of operator overloading in C++ and its applications in robotics projects.

Operator overloading is a powerful feature in C++ that allows you to define custom behavior for operators when applied to user-defined types. This means you can make operators like +, -, *, /, and others work with your own classes and objects in a way that makes sense for your specific use case.

Let’s create a new C++ file named distance.cpp to demonstrate operator overloading.

Type the following code into the editor:

#include <iostream>

class Distance {
public:
    int meters;

    Distance(int m = 0) : meters(m) {}

    Distance operator+(const Distance& other) const {
        return Distance(meters + other.meters);
    }
};

int main() {
    Distance d1(5);
    Distance d2(10);
    Distance sum = d1 + d2;
    std::cout << "d1 = " << d1.meters << " meters" << std::endl;
    std::cout << "d2 = " << d2.meters << " meters" << std::endl;
    std::cout << "sum = " << sum.meters << " meters" << std::endl;
    return 0;
}

In this code, we define a class called Distance to represent a distance in meters. We overload the + operator by defining the operator+() member function. This function takes another Distance object as a parameter and returns a new Distance object that is the sum of the two distances.

In the main() function, we create two Distance objects, d1 and d2, and compute their sum using the overloaded + operator. We then print the distances and their sum using the meters member variable.

Run the code.

2-operator-overloading

The output displays the distances d1 and d2, as well as their sum, in meters.

By overloading the + operator, we’ve made it possible to add two Distance objects together using the familiar syntax d1 + d2, which makes the code more intuitive and readable.

In robotics projects, operator overloading can be useful for performing operations on custom data types, such as distances, angles, or time durations, making the code more expressive and easier to understand.

Thanks, and I’ll see you in the next tutorial.

Keep building!