C++ Standard Library

Exploring Standard Library to Solve Common Tasks

Kyle Hurd


Example 1

Problem:

Given a list, generate a new list that contains all value squared.

Example:

Input: [1, 2, 3, 4] Output: [1, 4, 9, 16]


Naive Approach

1
2
3
4
5
std::vector<int> input{ 1, 2, 3, 4, };
std::vector<int> output;
for (const auto value : input) {
  output.push_back(value * value);
}



1
2
3
❯ g++-14 main.cpp
❯ ./a.out
[ 1, 4, 9, 16 ]

Standard Library Approach

1
2
3
4
5
std::vector<int> input{ 1, 2, 3, 4, };
auto output{ input };
std::transform(input.cbegin(), input.cend(), output.begin(), [](const auto& value) {
  return std::pow(value, 2);
});



1
2
3
❯ g++-14 main.cpp
❯ ./a.out
[ 1, 4, 9, 16 ]

Example 2

Problem:

Given a list, generate a new list that contains all value squared if and only if that value is odd.

Example:

Input: [1, 2, 3, 4] Output: [1, 2, 9, 4]


Naive Approach

1
2
3
4
5
6
7
8
9
10
std::vector<int> input{ 1, 2, 3, 4, };
std::vector<int> output;
for (const auto value : input) {
  if (value % 2 == 1) {
    output.push_back(value * value);
  }
  else {
    output.push_back(value);
  }
}
1
2
3
❯ g++-14 main.cpp
❯ ./a.out
[ 1, 2, 9, 4 ]

Standard Library Approach

1
2
3
4
5
std::vector<int> input{ 1, 2, 3, 4, };
auto output{ input };
std::transform(input.cbegin(), input.cend(), output.begin(), [](const auto& value) {
  return value % 2 == 1 ? std::pow(value, 2) : value;
});



1
2
3
❯ g++-14 main.cpp
❯ ./a.out
[ 1, 4, 9, 16 ]

Example 2

Problem:

Given a list of integers, return the sum of all numbers in the list.

Example:

Input: [1, 2, 3, 4] Output: 10


Naive Approach

1
2
3
4
5
std::vector<int> input{ 1, 2, 3, 4, };
int output{};
for (const auto value : input) {
  output += value;
}
1
2
3
❯ g++-14 main.cpp
❯ ./a.out
10

Standard Library Approach

1
2
std::vector<int> input{ 1, 2, 3, 4, };
const auto result{ std::accumulate(input.cbegin(), input.cend(), 0) };



1
2
3
❯ g++-14 main.cpp
❯ ./a.out
10

Standard For Each

Situation:

We have a method send_requestwhich takes a Request object and handles sending a request.

1
2
3
4
5
6
void send_request(const Request& request);

std::vector<Request> requests = ...;
for (const auto& request : requests) {
  send_request(request);
}

Standard For Each

In cases where the function being called matches the container perfectly, we can promote reusability and a functional approach by utilizing std::for_each

1
2
3
4
void send_request(const Request& request);

std::vector<Request> requests = ...;
std::for_each(requests.cbegin(), requests.cend(), send_request);

Standard For Each - Unique Use Cases

For situations where you have to maintain state, std::for_each is particularly powerful.

1
2
3
4
5
6
7
8
9
10
11
12
// Function object that counts even numbers
struct CountEven {
    int count = 0;
    void operator()(int n) {
        if (n % 2 == 0) {
            ++count;
        }
    }
};

const auto container = { 1, 2, 3, 4, 5, 6, 7, 8, };
const auto counter{ std::for_each(std::cbegin(container), std::cend(container), CountEven()) };
1
2
3
❯ g++-14 main.cpp
❯ ./a.out
Number of even elements: 4

Standard For Each - Parallel For Loops!

Before C++17, to parallelize a for loop, you would have to utilize a tool like OpenMP. Although OpenMP will most likely outperform the standard library, we have easy access to parallelizing for loops when it is needed for a performance bump.


Parallel For Loops!

Task: Double the Values in a Large Container

1
2
3
4
std::vector<int> container(1'000'000, 1);
for (auto& num : container) {
  num *= 2;
}

Approach 1: OpenMP

1
2
3
4
#pragma omp parallel for
for (int i = 0; i < container.size(); ++i) {
  container.at(i) *= 2;
}

Approach 2: Parallel std::for_each

1
2
3
std::for_each(std::execution::par, container.begin(), container.end(), [](auto& num) {
  return num * 2;
});

Example 3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
constexpr auto counter_limit{ 500 };
int counter{};

while (counter < counter_limit) {
  if (valid_state) {
    const auto count{ counter };
    counter = 0;
    handle_counts(count);
  }
  ++counter;
  check_a();
  check_b();
  // ...
}

Standard Library Approach

1
2
3
4
5
6
7
8
9
while (counter < counter_limit) {
  if (valid_state) {
    handle_counts(std::exchange(counter, 0));
  }
  ++counter;
  check_a();
  check_b();
  // ...
}

Example 4:

Problem:

Given a list of numbers, correct all of them such that all numbers are within the range 0 - 100

Example:

Input: [ -40, 24, 99, 100, 110, 114, 0 ] Output: [ 0, 24, 99, 100, 100, 100, 0 ]


Naive Approach

1
2
3
4
5
6
7
8
9
std::vector<int> numbers{ -40, 24, 99, 100, 110, 114, 0, };
for (auto& number : numbers) {
  if (number < 0) {
    number = 0;
  }
  if (number > 100) {
    number = 100;
  }
}
1
2
3
❯ g++-14 main.cpp
❯ ./a.out
[ 0, 24, 99, 100, 100, 100, 0 ]

Standard Library Approach

1
2
3
4
std::vector<int> numbers{ -40, 24, 99, 100, 110, 114, 0, };
std::for_each(numbers.begin(), numbers.end(), [](auto& value) {
  return std::clamp(value, 0, 100);
});



1
2
3
❯ g++-14 main.cpp
❯ ./a.out
[ 0, 24, 99, 100, 100, 100, 0 ]

Example 5

Problem:

Given a list of scores, determine if all scores are between the values 0 and 100.

Example:

Input: [ 32, 69, 42 ] Output: true

Input: [ 0, 42, 101 ] Output: false


Naive Approach

1
2
3
4
5
6
7
std::vector<int> scores{ 32, 69, 42, };
bool result{ true };
for (const auto score : scores) {
  if (score < 0 || score > 100) {
    result = false;
  }
}
1
2
3
❯ g++-14 main.cpp
❯ ./a.out
true

Standard Library Approach

1
2
3
4
std::vector<int> scores{ 32, 69, 42, };
const auto result = std::all_of(scores.cbegin(), scores.cend(), [](const auto score) {
  return score >= 0 && score <= 100;
});






1
2
3
❯ g++-14 main.cpp
❯ ./a.out
true

Example 5

Problem:

Given a container of numbers, erase all numbers that are even.

Example:

Input: [ 1, 1, 3, 4, 6, 8 ] Output: [ 1, 1, 3 ]


Naive Approach

1
2
3
4
5
6
7
8
9
10
std::vector<int> numbers{ 1, 1, 3, 4, 6, 8 };
auto it = numbers.begin();
while (it != numbers.end()) {
  if (*it % 2 == 0) {
    it = numbers.erase(it);
  }
  else {
    ++it;
  }
}
1
2
3
❯ g++-14 main.cpp
❯ ./a.out
[ 1, 1, 3 ]

Standard Library Approach

1
2
3
4
std::vector<int> numbers{ 1, 1, 3,4, 6, 8, };
numbers.erase(std::remove_if(numbers.begin(), numbers.end(), [](const auto number) {
  return number % 2 == 0;
}), numbers.end());
1
2
3
❯ g++-14 main.cpp
❯ ./a.out
[ 1, 1, 3 ]

Example 6

Problem:

You have a JSON configuration file that you want to load into your application. The JSON file consists of many types of values:




1
2
3
4
5
6
{
  "name": "Kyle Hurd",
  "age": 25,
  "is_married": false,
  "percent_done": 88.3
}

Naive Approach

1
2
3
4
5
6
7
8
9
10
11
12
class Config {
public:
  std::string get_value(const std::string& key) {
    return m_data.at(key);
  }
private:
  std::unordered_map<std::string, std::string> m_data;
};

Config config;
const auto age{ std::stoi(config.get_value("age")) };
const auto is_married{ config.get_value("is_married") == "true" };

Standard Library Approach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Config {
public:
  template <typename T>
  T get_value(const std::string& key) {
    return std::any_cast<T>(m_data.at(key).second);
  }
private:
  std::unordered_map<std::string, std::any> m_data;
};

Config config;
const auto name{ config.get_value<std::string>("name") };
const auto age{ config.get_value<int>("age") };
const auto is_married{ config.get_value<bool>("is_married") };