Modern C++ (C++11~C++20) Core Syntax Cheatsheet | auto
이 글의 핵심
Modern C++ C++11~C++20 core syntax cheatsheet. auto, range-for, lambda, smart pointers, optional, variant, Concepts, Ranges copy-paste ready summary.
Introduction
Modern C++ (C++11 onwards) introduced auto, lambda, smart pointers, optional, Concepts and many features. This guide is a cheatsheet compressing what’s really used daily in production for quick reference.
What You Will Learn
- Grasp C++11~C++20 core syntax at a glance
- Learn copy-paste ready patterns frequently used in production
- Compare major features by version
- Get code ready for coding tests and production
Table of Contents
- C++11: auto, range-for, lambda, smart pointers
- C++14: Generic lambda, make_unique
- C++17: Structured binding, optional, if constexpr
- C++20: Concepts, Ranges, Coroutine
- Real-World Cases
- Troubleshooting
- Conclusion
C++11: auto, range-for, lambda, smart pointers
1) auto - Type Deduction
auto x = 42; // int
auto d = 3.14; // double
auto s = std::string("hello"); // std::string
auto v = std::vector<int>{1, 2, 3}; // std::vector<int>
// Iterator
auto it = v.begin();
// Function return type
auto add(int a, int b) -> int {
return a + b;
}
2) range-based for
std::vector<int> v = {1, 2, 3, 4, 5};
// Read
for (int x : v) {
std::cout << x << std::endl;
}
// Modify
for (int& x : v) {
x *= 2;
}
// const reference
for (const auto& x : v) {
std::cout << x << std::endl;
}
3) Lambda Expression
// Basic
auto add = [](int a, int b) { return a + b; };
std::cout << add(2, 3) << std::endl; // 5
// Capture
int factor = 10;
auto multiply = [factor](int x) { return x * factor; };
std::cout << multiply(5) << std::endl; // 50
// Capture modes
[=] // Capture by value
[&] // Capture by reference
[x] // Capture x by value only
[&x] // Capture x by reference only
4) Smart Pointers
#include <memory>
// unique_ptr (single ownership)
auto p1 = std::make_unique<int>(42);
std::cout << *p1 << std::endl;
// shared_ptr (shared ownership)
auto p2 = std::make_shared<int>(42);
auto p3 = p2; // Increment reference count
// weak_ptr (prevent circular reference)
std::weak_ptr<int> wp = p2;
if (auto sp = wp.lock()) {
std::cout << *sp << std::endl;
}
5) nullptr
// C++98
int* p = NULL; // Confusing with 0
// C++11
int* p = nullptr; // Type-safe
6) Initializer List
std::vector<int> v = {1, 2, 3, 4, 5};
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
C++14: Generic lambda, make_unique
1) Generic Lambda
auto print = [](auto x) { std::cout << x << std::endl; };
print(42);
print("hello");
print(3.14);
2) make_unique
// C++11
std::unique_ptr<int> p(new int(42));
// C++14
auto p = std::make_unique<int>(42);
3) Return Type Deduction
auto add(int a, int b) {
return a + b; // int deduced
}
C++17: Structured binding, optional, if constexpr
1) Structured Binding
// pair
std::pair<int, std::string> p = {1, "hello"};
auto [id, name] = p;
std::cout << id << ", " << name << std::endl;
// map
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
for (const auto& [key, value] : m) {
std::cout << key << ": " << value << std::endl;
}
// tuple
std::tuple<int, double, std::string> t = {1, 3.14, "hello"};
auto [i, d, s] = t;
2) std::optional
#include <optional>
std::optional<int> find(const std::vector<int>& v, int target) {
for (int x : v) {
if (x == target) return x;
}
return std::nullopt;
}
auto result = find(v, 5);
if (result) {
std::cout << "Found: " << *result << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
// value_or
int value = result.value_or(0);
3) std::variant
#include <variant>
std::variant<int, double, std::string> v;
v = 42;
std::cout << std::get<int>(v) << std::endl;
v = 3.14;
std::cout << std::get<double>(v) << std::endl;
v = "hello";
std::cout << std::get<std::string>(v) << std::endl;
// Visitor pattern
std::visit([](auto&& arg) {
std::cout << arg << std::endl;
}, v);
4) if constexpr
template<typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integer: " << value << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Float: " << value << std::endl;
} else {
std::cout << "Other: " << value << std::endl;
}
}
process(42); // Integer
process(3.14); // Float
process("hello"); // Other
5) Class Template Argument Deduction
// C++14
std::pair<int, std::string> p(1, "hello");
// C++17
std::pair p(1, "hello"); // Type deduced
std::vector v{1, 2, 3}; // std::vector<int>
C++20: Concepts, Ranges, Coroutine
1) Concepts
#include <concepts>
template<std::integral T>
T add(T a, T b) {
return a + b;
}
// Custom concept
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
template<Addable T>
T sum(T a, T b) {
return a + b;
}
2) Ranges
#include <ranges>
#include <vector>
std::vector<int> v = {1, 2, 3, 4, 5};
// Filter even numbers
auto even = v | std::views::filter([](int x) { return x % 2 == 0; });
// Transform
auto doubled = v | std::views::transform([](int x) { return x * 2; });
// Chain
auto result = v
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * 2; });
3) Three-way Comparison Operator
#include <compare>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default;
};
Point p1{1, 2};
Point p2{1, 3};
if (p1 < p2) {
std::cout << "p1 < p2" << std::endl;
}
Real-World Cases
Migrating a C++03 Loop to Range-Based + Ranges
A typical modernization: an index-based loop with manual filtering becomes a single expressive pipeline, with each C++ era’s idiom layered on top of the last.
// C++03
std::vector<int> result;
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
if (*it % 2 == 0) result.push_back(*it * 2);
}
// C++11: range-for + auto
std::vector<int> result2;
for (auto x : v) {
if (x % 2 == 0) result2.push_back(x * 2);
}
// C++20: ranges pipeline, lazy-evaluated, no intermediate vector needed
auto result3 = v
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * 2; });
Constraining a Generic API with Concepts Instead of SFINAE
Replacing an enable_if template constraint with a concept keeps the same compile-time safety while producing a readable compiler error instead of a wall of SFINAE substitution failures.
// Before (C++11 SFINAE)
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
T clamp_positive(T value) { return value < 0 ? 0 : value; }
// After (C++20 concept)
template<std::integral T>
T clamp_positive(T value) { return value < 0 ? 0 : value; }
Structured Bindings for Map Iteration
A common cleanup in real codebases: replacing .first/.second access with structured bindings makes map/set iteration read like plain key-value pairs.
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}};
// Before
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << "\n";
}
// After (C++17 structured bindings)
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "\n";
}
Coroutine-Based Generator (C++20)
Coroutines let a function suspend and resume instead of running to completion, replacing hand-rolled iterator/state-machine boilerplate for lazy sequences. C++20 provides the co_yield/co_await keywords; a full generator type still needs a small amount of custom scaffolding until std::generator (C++23) is available.
#include <coroutine>
struct FibonacciGenerator {
struct promise_type {
int current_value;
FibonacciGenerator get_return_object() { return {this}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
void return_void() {}
void unhandled_exception() {}
};
promise_type* promise;
};
FibonacciGenerator fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
int next = a + b;
a = b;
b = next;
}
}
Troubleshooting
Problem 1: auto Overuse
Symptom: Code readability decreases
// ❌ Unclear type
auto x = 0; // int? long? size_t?
// ✅ Explicit type
int x = 0;
// Good auto usage
auto it = v.begin(); // Iterator
auto lambda = [](int x) { return x * 2; };
Problem 2: Lambda Capture Dangling
Symptom: Lambda captures invalid reference
// ❌ Dangling reference
auto makeLambda() {
int x = 42;
return [&x]() { return x; }; // x disappears from stack
}
// ✅ Capture by value
auto makeLambda() {
int x = 42;
return [x]() { return x; };
}
Problem 3: shared_ptr Circular Reference
Symptom: Memory leak
#include <memory>
struct Node {
std::shared_ptr<Node> next;
};
// ❌ Circular reference
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2;
n2->next = n1; // Circular reference (memory leak)
// ✅ Use weak_ptr
struct NodeFixed {
std::weak_ptr<NodeFixed> next;
};
Problem 4: optional Value Access Without Check
Symptom: std::bad_optional_access exception
std::optional<int> opt;
// ❌ Access without check
// int x = *opt; // Exception
// ✅ Check before access
if (opt) {
int x = *opt;
}
// Or value_or
int x = opt.value_or(0);
Conclusion
Modern C++ greatly improves code quality and productivity.
Key Summary
- C++11
- auto, range-for, lambda, smart pointers
- nullptr, initializer list
- C++14
- Generic lambda, make_unique
- Return type deduction
- C++17
- Structured binding, optional, variant
- if constexpr, class template argument deduction
- C++20
- Concepts, Ranges, Coroutine
- Three-way comparison operator
Adoption Order
| Stage | Feature | Reason |
|---|---|---|
| Stage 1 | auto, range-for, smart pointers | Bug reduction |
| Stage 2 | lambda, optional | Code conciseness |
| Stage 3 | Structured binding, variant | Readability |
| Stage 4 | Concepts, Ranges | Expressiveness |
Core Features by Version
// C++11
auto x = 42;
for (auto& x : v) { /* ... */ }
auto lambda = [](int x) { return x * 2; };
auto p = std::make_shared<int>(42);
// C++14
auto print = [](auto x) { std::cout << x; };
auto p = std::make_unique<int>(42);
// C++17
auto [key, value] = map.begin();
std::optional<int> opt = find(v, 5);
if constexpr (std::is_integral_v<T>) { /* ... */ }
// C++20
template<std::integral T>
T add(T a, T b) { return a + b; }
auto even = v | std::views::filter([](int x) { return x % 2 == 0; });
Next Steps
- auto and decltype: C++ auto and decltype
- Range-based for: C++ Range-based for
- C++ Overview: What is C++?
References
- “Effective Modern C++” - Scott Meyers
- “C++17 The Complete Guide” - Nicolai M. Josuttis
- “C++20 The Complete Guide” - Nicolai M. Josuttis
- cppreference: https://en.cppreference.com/ One-line summary: Modern C++ greatly improves code quality and productivity with auto, lambda, smart pointers, optional, and Concepts.
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Modern C++ C++11~C++20 core syntax cheatsheet. auto, range-for, lambda, smart pointers, optional, variant, Concepts, Ran…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문
- C++이란? 역사, 현황, 그리고 시작 전에 알아둘 것 | C++ 입문 가이드
- C++ 컴파일 타임 최적화 | constexpr·PCH·모듈·ccache·Unity 빌드 [#15-3]
Keywords Covered in This Article (Related Search Terms)
This article covers C++, Modern C++, C++11, C++14, C++17, C++20, cheatsheet.