| name | cpp-modern-features |
| user-invocable | false |
| description | Use when modern C++ features from C++11/14/17/20 including auto, lambdas, range-based loops, structured bindings, and concepts. |
| allowed-tools | ["Read","Write","Edit","Grep","Glob","Bash"] |
Modern C++ Features
Modern C++ (C++11 and beyond) introduced significant improvements that make
C++ more expressive, safer, and easier to use. This skill covers essential
modern features including type inference, lambda expressions, range-based
loops, smart initialization, and the latest C++20 additions.
Auto Type Inference
The auto keyword enables automatic type deduction, reducing verbosity while
maintaining type safety.
#include <iostream>
#include <vector>
#include <map>
#include <string>
void auto_examples() {
auto x = 42;
auto pi = 3.14159;
auto name = "Alice";
auto message = std::string("Hello");
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (std::vector<int>::iterator it = numbers.begin();
it != numbers.end(); ++it) {
std::cout << *it << " ";
}
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
std::map<std::string, std::vector<int>> data;
auto it = data.find("key");
auto multiply = [](int a, int b) { return a * b; };
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "\n";
}
}
Lambda Expressions
Lambdas provide inline anonymous functions, essential for modern C++
algorithms and callbacks.
#include <algorithm>
#include <vector>
#include <functional>
#include <iostream>
void lambda_examples() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
auto print = [](int n) { std::cout << n << " "; };
std::for_each(numbers.begin(), numbers.end(), print);
int threshold = 5;
auto above_threshold = [threshold](int n) { return n > threshold; };
auto sum_above = [=]() {
int sum = 0;
for (int n : numbers) {
if (n > threshold) sum += n;
}
return sum;
};
int count = 0;
auto count_above = [&count, threshold](int n) {
if (n > threshold) count++;
};
std::for_each(numbers.begin(), numbers.end(), count_above);
generic_print = []( & item) {
std::cout << item << ;
};
std::(numbers.(), numbers.(),
[]( a, b) { a > b; });
counter = [count = ]() {
++count;
};
std::cout << () << ;
std::cout << () << ;
}
{
[factor]( n) { n * factor; };
}
Range-Based For Loops
Range-based for loops provide clean, safe iteration over containers and
ranges.
#include <vector>
#include <map>
#include <string>
#include <iostream>
void range_based_loops() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int n : numbers) {
std::cout << n << " ";
}
for (int& n : numbers) {
n *= 2;
}
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
for (const auto& name : names) {
std::cout << name << "\n";
}
std::map<std::string, int> ages = {
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35}
};
for (const auto& [name, age] : ages) {
std::cout << name << " is " << age << " years old\n";
}
(std::vector<> temp = {, , }; n : temp) {
std::cout << n << ;
}
}
{
start_, end_;
:
( start, end) : (start), (end) {}
{
current;
( val) : (val) {}
*() { current; }
Iterator& ++() { ++current; *; }
!=( Iterator& other) {
current != other.current;
}
};
{ (start_); }
{ (end_); }
};
{
( i : (, )) {
std::cout << i << ;
}
}
Uniform Initialization
Uniform initialization using braces provides consistent syntax and prevents
narrowing conversions.
#include <vector>
#include <string>
#include <map>
struct Point {
int x, y;
};
void uniform_initialization() {
int a{42};
double pi{3.14159};
std::vector<int> numbers{1, 2, 3, 4, 5};
std::map<std::string, int> ages{
{"Alice", 30},
{"Bob", 25}
};
Point p{10, 20};
int x = 3.14;
int zero{};
std::string empty{};
auto get_numbers = []() { return std::vector<int>{1, 2, 3}; };
}
{
:
() = ;
( x) {}
};
{
Widget w{};
Widget w2{};
}
Move Semantics and Rvalue References
Move semantics enable efficient transfer of resources without copying,
crucial for performance.
#include <vector>
#include <string>
#include <utility>
#include <iostream>
class Buffer {
size_t size_;
int* data_;
public:
Buffer(size_t size) : size_(size), data_(new int[size]) {
std::cout << "Constructor\n";
}
Buffer(const Buffer& other)
: size_(other.size_), data_(new int[other.size_]) {
std::copy(other.data_, other.data_ + size_, data_);
std::cout << "Copy constructor\n";
}
Buffer(Buffer&& other) noexcept
: size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr;
std::cout << "Move constructor\n";
}
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new [size_];
std::(other.data_, other.data_ + size_, data_);
std::cout << ;
}
*;
}
Buffer& =(Buffer&& other) {
( != &other) {
[] data_;
size_ = other.size_;
data_ = other.data_;
other.size_ = ;
other.data_ = ;
std::cout << ;
}
*;
}
~() { [] data_; }
};
{
;
Buffer b2 = std::(b1);
std::vector<Buffer> buffers;
buffers.(());
make_buffer = [](&&... args) {
(std::forward<(args)>(args)...);
};
}
Variadic Templates
Variadic templates enable functions and classes that accept any number of
arguments.
#include <iostream>
#include <string>
void print() {
std::cout << "\n";
}
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...);
}
template<typename... Args>
auto sum(Args... args) {
return (args + ...);
}
template<typename... Args>
auto sum_with_init(Args... args) {
return (args + ... + 0);
}
template<typename T, typename... Args>
std::unique_ptr<T> make_unique_custom(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
void variadic_examples() {
print(, , , std::());
total = (, , , , );
all_true = [](... args) {
(args && ...);
};
any_true = [](... args) {
(args || ...);
};
}
Structured Bindings (C++17)
Structured bindings decompose objects into their constituent parts,
improving code readability.
#include <tuple>
#include <map>
#include <string>
#include <array>
struct Person {
std::string name;
int age;
double salary;
};
std::tuple<int, std::string, double> get_employee() {
return {42, "Alice", 75000.0};
}
void structured_bindings() {
auto [id, name, salary] = get_employee();
std::pair<int, std::string> p{1, "one"};
auto [num, text] = p;
Person person{"Bob", 30, 80000.0};
auto [pname, page, psalary] = person;
std::array<int, 3> arr{1, 2, 3};
auto [a, b, c] = arr;
std::map<std::string, int> scores{{"Alice", 95}, {"Bob", }};
( & [name, score] : scores) {
std::cout << name << << score << ;
}
& [rname, rage, rsalary] = person;
rage = ;
}
Concepts (C++20)
Concepts constrain template parameters, providing better error messages and
clearer interfaces.
#include <concepts>
#include <iostream>
#include <vector>
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<Numeric T>
T add(T a, T b) {
return a + b;
}
template<typename T>
concept Printable = requires(T t) {
{ std::cout << t } -> std::convertible_to<std::ostream&>;
};
template<Printable T>
void print(const T& value) {
std::cout << value << "\n";
}
template<typename T>
concept Range = requires(T r) {
r.begin();
r.end();
};
template<Range R>
void print_range(const R& range) {
for (const auto& item : range) {
std::cout << item << " ";
}
std::cout << "\n";
}
template< T>
Container = (T c) {
T::value_type;
T::iterator;
{ c.() } -> std::same_as< T::iterator>;
{ c.() } -> std::same_as< T::iterator>;
{ c.() } -> std::convertible_to<std::>;
};
{
std::cout << << container.() << ;
}
{
result = (, );
dresult = (, );
();
();
std::vector<> vec{, , };
(vec);
(vec);
}
Ranges Library (C++20)
The ranges library provides composable algorithms and views for working with
sequences.
#include <ranges>
#include <vector>
#include <iostream>
#include <algorithm>
void ranges_examples() {
std::vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto even = [](int n) { return n % 2 == 0; };
auto square = [](int n) { return n * n; };
auto result = numbers
| std::views::filter(even)
| std::views::transform(square)
| std::views::take(3);
for (int n : result) {
std::cout << n << " ";
}
std::cout << "\n";
std::ranges::sort(numbers, std::greater{});
struct Person {
std::string name;
age;
};
std::vector<Person> people{
{, },
{, },
{, }
};
it = std::ranges::(people, , &Person::name);
( i : std::views::(, )) {
std::cout << i << ;
}
std::cout << ;
std::string text = ;
( word : text | std::views::()) {
( c : word) {
std::cout << c;
}
std::cout << ;
}
}
Best Practices
- Use
auto for complex types and iterators but keep simple types explicit
- Prefer lambdas over function objects for inline operations and callbacks
- Use range-based for loops instead of manual iterator manipulation
- Initialize variables with
{} to prevent narrowing conversions
- Implement move constructors and assignments for resource-owning classes
- Use
std::move when transferring ownership, not for general optimization
- Prefer structured bindings over
std::get<>() for tuples and pairs
- Use concepts to constrain templates and improve error messages
- Leverage ranges for composable, lazy operations on sequences
- Use
const auto& for range-based loops with large objects
Common Pitfalls
- Overusing
auto making code less readable when types provide clarity
- Capturing by reference in lambdas that outlive their captures
- Using
std::move on const objects, which disables move semantics
- Forgetting
noexcept on move operations, preventing optimizations
- Modifying containers while iterating with range-based for loops
- Dangling references from structured bindings of temporary objects
- Using fold expressions without considering operator precedence
- Assuming ranges views create copies instead of providing lazy views
- Moving from objects that will be used again later
- Not marking move constructors and assignments as
noexcept
When to Use Modern C++ Features
Use modern C++ features when you need:
- Cleaner, more expressive code with less boilerplate
- Better type safety with concepts and structured bindings
- Improved performance through move semantics
- Functional programming patterns with lambdas and ranges
- Generic programming with less template complexity
- Safer resource management with smart pointers
- Code that's easier to maintain and refactor
- Better compiler error messages with concepts
- Lazy evaluation and composition with ranges
- Migration from older C++ codebases to modern standards
Resources