Modern C++ Language Features


When you are writing C++ code, especially on your interviews, you want to impress the interviewer with the modern C++ features.

Modern C++ introduces several powerful features, including algorithms like std::transform and std::for_each, which leverage functional programming paradigms for concise and expressive code.

For example: the advantages of std::for_each and std::transform:

  • Declarative: These functions describe what should happen, rather than how.
  • Cleaner code: They simplify loops, making code shorter and more expressive.
  • Parallelizable: With C++17 and later, the algorithms can be parallelized by using execution policies like std::execution::par.

Here’s an overview of these functions and their typical use cases.

std::for_each

std::for_each applies a function or callable object to every element in a given range. It is available in the algorithm header.

Syntax:

1
std::for_each(start, end, function);
std::for_each(start, end, function);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
 
    // Apply a lambda function to each element
    std::for_each(numbers.begin(), numbers.end(), [](int& num) {
        num *= 2; // Doubling each number
    });
 
    // Print the transformed vector
    for (const int& num : numbers) {
        std::cout << num << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // Apply a lambda function to each element
    std::for_each(numbers.begin(), numbers.end(), [](int& num) {
        num *= 2; // Doubling each number
    });

    // Print the transformed vector
    for (const int& num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

Output:

2 4 6 8 10

std::transform

std::transform applies a function to a range and stores the result in another range. It can either operate on a single range or on two input ranges (for element-wise transformations).

Syntax:

1
2
3
4
5
6
7
8
9
// Single range
std::transform(start, end, destination, unary_op);
 
// Two ranges
std::transform(start1, end1, start2, destination, binary_op);
// start1, end1: The first input range.
// start2: The second input range (must be at least as long as the first).
// destination: The destination range where results are stored.
// binary_op: A function that takes two arguments and returns a result.
// Single range
std::transform(start, end, destination, unary_op);

// Two ranges
std::transform(start1, end1, start2, destination, binary_op);
// start1, end1: The first input range.
// start2: The second input range (must be at least as long as the first).
// destination: The destination range where results are stored.
// binary_op: A function that takes two arguments and returns a result.

Example (single range):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<int> doubled_numbers(numbers.size());
 
    // Double each element
    std::transform(numbers.begin(), numbers.end(), doubled_numbers.begin(), [](int num) {
        return num * 2;
    });
 
    for (const int& num : doubled_numbers) {
        std::cout << num << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<int> doubled_numbers(numbers.size());

    // Double each element
    std::transform(numbers.begin(), numbers.end(), doubled_numbers.begin(), [](int num) {
        return num * 2;
    });

    for (const int& num : doubled_numbers) {
        std::cout << num << " ";
    }

    return 0;
}

Output:

2 4 6 8 10

Using std::transform with two ranges allows you to apply a binary operation to corresponding elements from two input ranges and store the result in a third range. This is useful for operations like element-wise addition, multiplication, or comparison.

This example demonstrates how to add corresponding elements of two vectors and store the results in a third vector.

Example: Element-wise addition of two vectors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> vec1 = {1, 2, 3, 4, 5};
    std::vector<int> vec2 = {10, 20, 30, 40, 50};
    std::vector<int> result(vec1.size());
 
    // Apply element-wise addition using std::transform
    std::transform(vec1.begin(), vec1.end(), vec2.begin(), result.begin(), [](int a, int b) {
        return a + b;
    });
 
    // Print the result vector
    for (const int& val : result) {
        std::cout << val << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec1 = {1, 2, 3, 4, 5};
    std::vector<int> vec2 = {10, 20, 30, 40, 50};
    std::vector<int> result(vec1.size());

    // Apply element-wise addition using std::transform
    std::transform(vec1.begin(), vec1.end(), vec2.begin(), result.begin(), [](int a, int b) {
        return a + b;
    });

    // Print the result vector
    for (const int& val : result) {
        std::cout << val << " ";
    }

    return 0;
}

Output:

11 22 33 44 55

Explanations:

  • vec1 and vec2 are the two input vectors.
  • result is the vector that will store the results of element-wise addition.
  • std::transform takes two input ranges (vec1.begin() and vec2.begin()) and applies the lambda function that adds corresponding elements.
  • The resulting values are stored in the result vector.

This technique can be adapted for any binary operation such as subtraction, multiplication, or custom operations like finding the maximum of two elements.

Modern Lambda Support

Both std::for_each and std::transform work seamlessly with C++ lambdas, enabling you to inline small operations without defining separate functions.

std::execution::par (C++17+)

In C++17, the parallel execution policies were introduced. This allows algorithms like std::for_each and std::transform to execute in parallel, improving performance on multi-core systems.

Example with parallel execution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <algorithm>
#include <execution>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    
    // Apply a transformation in parallel
    std::for_each(std::execution::par, numbers.begin(), numbers.end(), [](int& num) {
        num *= 2;
    });
 
    for (const int& num : numbers) {
        std::cout << num << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
#include <execution>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    
    // Apply a transformation in parallel
    std::for_each(std::execution::par, numbers.begin(), numbers.end(), [](int& num) {
        num *= 2;
    });

    for (const int& num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

std::count_if

std::count_if counts the number of elements in a range that satisfy a given predicate. This is useful when you want to know how many elements match certain criteria.

Syntax:

1
std::count_if(start, end, predicate);
std::count_if(start, end, predicate);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
    // Count how many numbers are even
    int count = std::count_if(numbers.begin(), numbers.end(), [](int num) {
        return num % 2 == 0;
    });
 
    std::cout << "Even numbers count: " << count << std::endl;
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // Count how many numbers are even
    int count = std::count_if(numbers.begin(), numbers.end(), [](int num) {
        return num % 2 == 0;
    });

    std::cout << "Even numbers count: " << count << std::endl;

    return 0;
}

Output:

1
Even numbers count: 5
Even numbers count: 5

std::find_if

std::find_if searches for the first element in a range that satisfies a given predicate and returns an iterator to it. If no such element is found, it returns the end iterator.

Syntax:

1
std::find_if(start, end, predicate);
std::find_if(start, end, predicate);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
    // Find the first number greater than 5
    auto it = std::find_if(numbers.begin(), numbers.end(), [](int num) {
        return num > 5;
    });
 
    if (it != numbers.end()) {
        std::cout << "First number greater than 5: " << *it << std::endl;
    } else {
        std::cout << "No number greater than 5 found." << std::endl;
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // Find the first number greater than 5
    auto it = std::find_if(numbers.begin(), numbers.end(), [](int num) {
        return num > 5;
    });

    if (it != numbers.end()) {
        std::cout << "First number greater than 5: " << *it << std::endl;
    } else {
        std::cout << "No number greater than 5 found." << std::endl;
    }

    return 0;
}

Output:

1
First number greater than 5: 6
First number greater than 5: 6

std::remove_if

std::remove_if reorders the elements in a range, moving elements that satisfy a predicate to the end, and returns an iterator to the new end of the range. It doesn’t actually remove the elements from the container but provides a convenient way to do so when combined with the container’s erase method.

Syntax:

1
std::remove_if(start, end, predicate);
std::remove_if(start, end, predicate);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
    // Remove all odd numbers
    auto new_end = std::remove_if(numbers.begin(), numbers.end(), [](int num) {
        return num % 2 != 0;
    });
 
    numbers.erase(new_end, numbers.end()); // Actually remove the elements
 
    // Print the modified vector
    for (const int& num : numbers) {
        std::cout << num << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // Remove all odd numbers
    auto new_end = std::remove_if(numbers.begin(), numbers.end(), [](int num) {
        return num % 2 != 0;
    });

    numbers.erase(new_end, numbers.end()); // Actually remove the elements

    // Print the modified vector
    for (const int& num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

Output:

1
2 4 6 8 10
2 4 6 8 10

std::accumulate (from <numeric>)

std::accumulate is used to fold (accumulate) a sequence into a single value, by applying a binary operation (e.g., sum, product) from the left to right.

Syntax:

1
std::accumulate(start, end, init, binary_op);
std::accumulate(start, end, init, binary_op);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <vector>
#include <numeric>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
 
    // Sum all the numbers
    int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
 
    std::cout << "Sum: " << sum << std::endl;
 
    return 0;
}
#include <iostream>
#include <vector>
#include <numeric>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // Sum all the numbers
    int sum = std::accumulate(numbers.begin(), numbers.end(), 0);

    std::cout << "Sum: " << sum << std::endl;

    return 0;
}

Output:

1
Sum: 15
Sum: 15

std::partition

std::partition reorders the range so that elements satisfying the predicate appear before those that don’t. It returns an iterator to the first element that does not satisfy the predicate.

Syntax:

1
std::partition(start, end, predicate);
std::partition(start, end, predicate);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
    // Partition the numbers so that all even numbers come first
    std::partition(numbers.begin(), numbers.end(), [](int num) {
        return num % 2 == 0;
    });
 
    // Print the partitioned vector
    for (const int& num : numbers) {
        std::cout << num << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // Partition the numbers so that all even numbers come first
    std::partition(numbers.begin(), numbers.end(), [](int num) {
        return num % 2 == 0;
    });

    // Print the partitioned vector
    for (const int& num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

Output:

2 4 6 8 10 1 3 5 7 9

std::sort

std::sort sorts a range using a comparator. By default, it sorts in ascending order, but you can specify a custom comparator (e.g., to sort in descending order). It is based on Quicksort algorithm which has in general a O(NLogN) time complexity.

Syntax:

1
2
std::sort(start, end);
std::sort(start, end, comparator);
std::sort(start, end);
std::sort(start, end, comparator);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {5, 3, 9, 1, 6};
 
    // Sort in ascending order
    std::sort(numbers.begin(), numbers.end());
 
    for (const int& num : numbers) {
        std::cout << num << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {5, 3, 9, 1, 6};

    // Sort in ascending order
    std::sort(numbers.begin(), numbers.end());

    for (const int& num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

Output:

1 3 5 6 9

Using a comparator is straightforward:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {5, 3, 9, 1, 6};
 
    // Sort in ascending order
    std::sort(numbers.begin(), numbers.end(), [](auto a, auto &b) { return a < b; });
 
    for (const int& num : numbers) {
        std::cout << num << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {5, 3, 9, 1, 6};

    // Sort in ascending order
    std::sort(numbers.begin(), numbers.end(), [](auto a, auto &b) { return a < b; });

    for (const int& num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

std::unique

std::unique removes consecutive duplicate elements from a sorted range, returning an iterator to the new end of the unique elements. This usually works with std::sort before applying std::unique.

Syntax:

1
std::unique(start, end);
std::unique(start, end);

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {1, 2, 2, 3, 4, 4, 5};
 
    // Remove consecutive duplicates
    auto new_end = std::unique(numbers.begin(), numbers.end());
 
    // Erase the duplicates
    numbers.erase(new_end, numbers.end());
 
    for (const int& num : numbers) {
        std::cout << num << " ";
    }
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 2, 3, 4, 4, 5};

    // Remove consecutive duplicates
    auto new_end = std::unique(numbers.begin(), numbers.end());

    // Erase the duplicates
    numbers.erase(new_end, numbers.end());

    for (const int& num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

Output:

1 2 3 4 5

std::all_of, std::any_of, std::none_of

These three functions check whether all, any, or none of the elements in a range satisfy a given predicate.

Syntax:

1
2
3
std::all_of(start, end, predicate);
std::any_of(start, end, predicate);
std::none_of(start, end, predicate);
std::all_of(start, end, predicate);
std::any_of(start, end, predicate);
std::none_of(start, end, predicate);

Example (std::all_of):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {2, 4, 6, 8};
 
    // Check if all numbers are even
    bool all_even = std::all_of(numbers.begin(), numbers.end(), [](int num) {
        return num % 2 == 0;
    });
 
    std::cout << "All numbers are even: " << (all_even ? "Yes" : "No") << std::endl;
 
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {2, 4, 6, 8};

    // Check if all numbers are even
    bool all_even = std::all_of(numbers.begin(), numbers.end(), [](int num) {
        return num % 2 == 0;
    });

    std::cout << "All numbers are even: " << (all_even ? "Yes" : "No") << std::endl;

    return 0;
}

Output:

All numbers are even: Yes

Summary of Modern C++ Algorithms

  • Element counting: std::count, std::count_if
  • Element finding: std::find, std::find_if
  • Transforming sequences: std::for_each, std::transform
  • Accumulate: std::accumulate
  • Removing elements: std::remove_if, std::unique
  • Partitioning: std::partition
  • Sorting: std::sort
  • Logical checks: std::all_of, std::any_of, std::none_of

These algorithms help make your code more concise, expressive, and maintainable while using C++’s powerful functional-style paradigms.

–EOF (The Ultimate Computing & Technology Blog) —

GD Star Rating
loading...
2067 words
Last Post: Comparisions of push_back and emplace_back in C++ std::vector

The Permanent URL is: Modern C++ Language Features

Leave a Reply