Modern C++ Hello World
If you learned older C++, this file shows the "new default style" in one place. Classic code often repeated types everywhere, passed strings in ways that copied more than needed, and built output with verbose stream chains.
Modern C++ gives us cleaner tools: - auto (C++11) to remove obvious type repetition - std::string_view (C++17) for non-owning string parameters - structured bindings (C++17) for readable unpacking - std::format (C++20) for safer, clearer formatting
Frequently Asked Questions
QWhen should I NOT use auto?
QWhat is the difference between std::string_view and std::string?
QDoes auto deduce references and const automatically?
QIs std::format available in all compilers today?
QCan string_view be used with C-style strings (const char*)?
#include <iostream>
#include <string>
#include <string_view>
#include <format>1 auto -- let the compiler deduce the type
auto deduces a variable's type from its initializer expression.
Use this when the initializer already makes the type clear.
It removes redundant type spelling while preserving static type safety.
Write auto name = initializer; and use const auto& for non-owning reads.
Read this left to right: "I need a value here; the initializer tells the compiler exactly what type it should be." This makes local code shorter and easier to scan.
Use "const auto&" when you need them preserved.
Watch out: auto strips top-level const and references.
2 string_view -- a lightweight, non-owning view of a string
std::string_view is a non-owning view over contiguous characters.
Use this for read-only string parameters when ownership should not transfer.
It avoids allocations and copies compared with pass-by-value strings.
Take std::string_view in APIs and ensure referenced storage outlives the view.
Think of string_view as a read-only "window" into text that already exists elsewhere. That makes it a great default for input parameters that only need to read.
Never return a string_view to a local std::string.
Watch out: the viewed string must outlive the string_view.
auto greet(std::string_view name) -> std::string {
return std::format("Hello, {}! Welcome to Modern C++.", name);
}3 Trailing return type (auto -> Type)
A trailing return type places the return type after the parameter list.
Use this when the return type depends on parameters or is clearer at the end.
It improves readability in template-heavy signatures.
Write auto fn(args) -> ReturnType.
For simple functions this is mostly a style choice. For templates and decltype-heavy signatures, it can make declarations much easier to read because the function name appears earlier.
auto add(int a, int b) -> int {
return a + b;
}4 Structured bindings (C++17) -- unpack aggregates
Structured bindings unpack tuple-like or aggregate values into named variables.
Use this when you need readable names for pair/tuple/struct elements.
It removes boilerplate like .first and .second.
Write auto [a, b] = value; or auto& [a, b] = value; to bind by reference.
This is mainly about readability. Instead of "pair.first" and "pair.second", give values meaningful names right away.
Use "auto& [x, y] = ..." to bind by reference.
Watch out: structured bindings create copies by default.
Key Takeaways
- Prefer auto when the type is obvious from the initializer.
- Use string_view for read-only string parameters to avoid copies.
- Use std::format for clear, type-safe string formatting.
- Use structured bindings to replace .first/.second boilerplate.
int main() {
// auto deduces std::string from greet()'s return type
auto message = greet("World");
std::cout << message << '\n';
// Trailing return type -- same result as "int add(int, int)"
std::cout << std::format("add(3, 4) = {}\n", add(3, 4));
// Structured bindings: unpack a pair without .first/.second
auto [x, y] = std::pair{10, 20};
std::cout << std::format("x = {}, y = {}\n", x, y);
// Structured bindings with an array
int arr[] = {100, 200, 300};
auto [a, b, c] = arr;
std::cout << std::format("a = {}, b = {}, c = {}\n", a, b, c);
// const auto& binding -- avoids copying
const auto& [cx, cy] = std::pair{42, 99};
std::cout << std::format("cx = {}, cy = {}\n", cx, cy);
return 0;
}General Discussion
This lesson feels like a good "new baseline" for writing C++ today. None of the features here are advanced by themselves, but together they make everyday code much easier to read.
The most practical habit to build first is probably string_view for read-only inputs and auto for obvious local variables. Those two changes immediately reduce noise without reducing clarity.
Structured bindings and std::format are mostly readability wins, but they also reduce small mistakes (wrong pair member, formatting mismatch), which adds up over time in real projects.