Question
C++17 is considered feature-complete, which means major changes are unlikely at this stage. Many proposals were discussed for the standard.
Which features were ultimately added to C++17?
Also, if I use a compiler that currently supports C++1z, which of those features should be expected to remain available once that compiler updates to the finalized C++17 standard?
Short Answer
By the end of this page, you will understand what C++17 introduced, why those additions matter, and how to recognize the most practical language and standard library features in real code. This is not just a list of changes: it explains the ideas behind the most useful C++17 features, including structured bindings, if constexpr, std::optional, std::variant, std::string_view, fold expressions, filesystem support, and more.
Concept
C++17 is a version of the C++ language standard that added both language features and standard library improvements. A C++ standard defines what syntax the language supports and what tools the standard library provides.
Before C++17, developers often had to write more boilerplate code for common tasks such as:
- unpacking values from pairs or tuples
- writing template code with many special cases
- returning values that may or may not exist
- working with file paths in a portable way
- passing read-only string data efficiently
C++17 improves these areas by making code:
- clearer
- shorter
- safer
- more expressive
Two kinds of C++17 features
1. Language features
These change the core syntax of C++. Examples:
- structured bindings
if constexpr- fold expressions
- inline variables
- nested namespace definitions
2. Standard library features
These add new classes, functions, and utilities. Examples:
std::optionalstd::variantstd::anystd::string_viewstd::filesystem
What does C++1z mean?
Before the standard was finalized, compilers often used the temporary name C++1z for the upcoming standard. Once finalized, that became C++17.
So if a compiler supported -std=c++1z, it generally meant it supported some or many draft C++17 features. After the standard was finalized, the final switch became -std=c++17.
However, support was not always complete at the same time for every feature. A compiler might support:
- some language features early
- some library features later
- draft behavior that changed slightly before final standardization
That is why C++17 support is usually discussed feature by feature, not just by compiler version.
Important C++17 features to know
Structured bindings
Let you unpack tuple-like values into named variables.
auto [id, name] = get_user();
This improves readability when working with std::pair, std::tuple, or map elements.
if constexpr
Lets templates choose code paths at compile time.
if constexpr (std::is_integral_v<T>) {
// integer-specific code
}
Unlike a normal if, the unused branch is discarded during compilation.
Fold expressions
Simplify template code that works with parameter packs.
return (... + args);
This replaces more complex recursive template patterns.
std::optional
Represents a value that might be present or absent.
std::optional<int> find_id();
This is often better than using special sentinel values like -1.
std::variant
Represents one value from a fixed set of types.
std::variant<int, std::string> value;
Useful when a variable may legally hold different kinds of data.
std::string_view
Provides a lightweight, non-owning view into a string.
void print(std::string_view text);
This avoids unnecessary string copying.
std::filesystem
Adds standard support for paths, files, and directories.
std::filesystem::path p = "data/file.txt";
This replaced many platform-specific approaches.
Inline variables
Allow global or static variables in headers without multiple definition problems.
inline const int version = 17;
Class template argument deduction (CTAD)
Allows the compiler to infer template arguments in some cases.
std::pair p(1, 2.5);
Nested namespace definitions
Shorter syntax for nested namespaces.
namespace app::network::http {
}
Why this matters
In real programs, C++17 helps reduce manual work and improves correctness. Modern C++ codebases commonly rely on these features because they make APIs easier to use and templates easier to write.
Mental Model
Think of C++17 as a toolbox upgrade.
Before C++17, you already had tools, but some jobs took extra steps:
- opening a box inside a box inside a box to get values out of a tuple
- writing long template machinery to handle simple compile-time decisions
- using awkward placeholders like
-1ornullptrto mean “no result” - manually joining file paths in operating-system-specific ways
C++17 gives you better tools for those same jobs:
- structured bindings are like unpacking a delivery box and labeling each item immediately
if constexpris like a smart factory machine that permanently removes the wrong branch before production startsstd::optionalis like a container that clearly says “may contain one value, or may be empty”std::variantis like a storage slot with a label saying which of several allowed item types is currently insidestd::string_viewis like borrowing a page to read it without photocopying the whole book
The big idea is this: C++17 lets you express intent more directly. Instead of encoding meaning with tricks, you use language and library features that say exactly what you mean.
Syntax and Examples
Core syntax examples
Structured bindings
#include <iostream>
#include <tuple>
std::tuple<int, double> get_data() {
return {42, 3.14};
}
int main() {
auto [count, value] = get_data();
std::cout << count << ", " << value << '\n';
}
This extracts tuple elements into named variables.
if constexpr
#include <iostream>
#include <type_traits>
template <typename T>
void print_type_info(const T& value) {
if constexpr (std::is_integral_v<T>) {
std::cout << value << " is an integer\n";
} else {
std::cout << value << " is not an integer\n";
}
}
int main() {
print_type_info(10);
print_type_info(3.5);
}
The branch is chosen at compile time based on the type.
Fold expressions
#include <iostream>
template <typename... Args>
auto sum(Args... args) {
return (... + args);
}
int main() {
std::cout << sum(1, 2, 3, 4) << '\n';
}
This adds all arguments together without recursive templates.
std::optional
#include <iostream>
#include <optional>
std::optional<int> divide(int a, int b) {
if (b == 0) {
return std::nullopt;
}
return a / b;
}
int main() {
auto result = divide(10, 2);
if (result) {
std::cout << *result << '\n';
}
}
This makes absence of a value explicit.
std::string_view
#include <iostream>
#include <string>
#include <string_view>
void greet(std::string_view name) {
std::cout << "Hello, " << name << '\n';
}
int main() {
std::string s = "Alice";
greet(s);
greet("Bob");
}
This function can accept string literals and std::string efficiently.
std::variant
#include <iostream>
#include <string>
#include <variant>
int main() {
std::variant<int, std::string> data;
data = 42;
std::cout << std::get<int>(data) << '\n';
data = std::string("hello");
std::cout << std::get<std::string>(data) << '\n';
}
A variant stores one of several allowed types.
Other notable C++17 additions
- inline variables
- class template argument deduction (CTAD)
- nested namespace syntax
- constexpr lambdas
[[fallthrough]],[[nodiscard]],[[maybe_unused]]attributes- guaranteed copy elision in some cases
std::anystd::byte- parallel algorithms
std::invokestd::applystd::clampstd::shared_mutexstd::from_chars/std::to_charsstd::scoped_lock- filesystem library
Step by Step Execution
Trace example: structured bindings with a map
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<int, std::string> users = {
{1, "Alice"},
{2, "Bob"}
};
for (const auto& [id, name] : users) {
std::cout << id << " -> " << name << '\n';
}
}
What happens step by step
- A
std::map<int, std::string>is created. - It stores key-value pairs:
1 -> "Alice"2 -> "Bob"
- The
forloop iterates through each map element. - Each map element is internally a pair-like object.
const auto& [id, name]uses structured bindings to unpack that pair.- On the first iteration:
idbecomes1namebecomes"Alice"
- The program prints
1 -> Alice. - On the second iteration:
idbecomes2namebecomes"Bob"
- The program prints
2 -> Bob.
Why this is useful
Before C++17, you would often write something like this:
for (const auto& entry : users) {
std::cout << entry.first << " -> " << entry.second << '\n';
}
That works, but structured bindings make the code easier to read because the names id and name are more meaningful than first and second.
Real World Use Cases
Where C++17 features are used in practice
Parsing and validation
std::optionalis used when a function may fail to produce a value.- Example: parsing an integer from user input.
std::optional<int> parse_port(const std::string& text);
API responses and state handling
std::variantis useful when a result can be one of several known types.- Example: a parser returns either a number, a string, or a boolean token.
Efficient string handling
std::string_viewis common in logging, configuration loading, and text processing.- It avoids copying when reading parts of existing strings.
File and path operations
std::filesystemis used in:- backup tools
- build systems
- game asset loaders
- command-line utilities
Generic libraries and templates
if constexprand fold expressions are widely used in reusable utility code.- They simplify template metaprogramming and reduce boilerplate.
Safer interfaces
[[nodiscard]]helps prevent bugs by warning when important return values are ignored.
[[nodiscard]] bool save_file(const std::string& path);
Concurrent programming
std::scoped_lockandstd::shared_mutexare used to write safer multithreaded code.
Container and algorithm glue code
- structured bindings are frequently used with maps, tuples, and return values from helper functions.
Real Codebase Usage
Common patterns in real projects
Guard clauses with std::optional
Developers often use optional for validation results.
std::optional<int> parse_age(const std::string& text);
void register_user(const std::string& input) {
auto age = parse_age(input);
if (!age) {
return;
}
// use *age
}
This avoids magic values such as -1.
Early branching with if constexpr
Template utilities often choose behavior based on type.
template <typename T>
std::string to_text(const T& value) {
if constexpr (std::is_same_v<T, std::string>) {
return value;
} else {
return std::to_string(value);
}
}
Cleaner loops with structured bindings
Map iteration becomes clearer.
for (const auto& [key, value] : settings) {
// use key and value
}
Configuration and parsing with string_view
Functions that only read string data often accept std::string_view.
bool is_enabled(std::string_view flag_name);
Error and result modeling with variant
Projects sometimes represent different result states with a variant.
using ConfigValue = std::variant<int, bool, std::string>;
Header constants with inline variables
Shared constants in headers are cleaner in C++17.
inline constexpr int max_connections = 100;
Filesystem-based tools
Build tools, importers, test runners, and CLI apps commonly use std::filesystem::path for portability.
Practical note about compiler support
In real codebases, teams usually check both:
- the compiler version
- the standard library version
A compiler may support the C++17 language mode, while some library features like parts of filesystem arrived later depending on the toolchain.
Common Mistakes
1. Assuming C++1z always means full final C++17 support
Draft support and final standard support are not always identical.
Better approach
Check the compiler documentation or feature test macros for specific features.
2. Misusing std::string_view
string_view does not own the string data.
Broken example
#include <string>
#include <string_view>
std::string_view bad() {
return std::string("temporary");
}
The returned string_view points to destroyed data.
Fix
Make sure the viewed string outlives the string_view.
std::string good_source() {
return "text";
}
Or pass string_view only for temporary reading, not long-term storage.
3. Calling std::get on the wrong variant type
Broken example
std::variant<int, std::string> v = "hello";
int x = std::get<int>(v);
This throws std::bad_variant_access.
Fix
Check the active type first or use std::visit.
4. Forgetting to check std::optional
Broken example
std::optional<int> value;
int x = *value;
Dereferencing an empty optional is an error.
Fix
if (value) {
int x = *value;
}
5. Using normal if where if constexpr is needed in templates
A regular if still requires both branches to be valid during compilation.
Broken idea
template <typename T>
void f(T value) {
if (std::is_integral_v<T>) {
value.nonexistent_member();
}
}
Even if the condition is false, compilation may fail.
Fix
template <typename T>
void f(T value) {
if constexpr (std::is_integral_v<T>) {
// integral-only code
}
}
6. Expecting every C++17 library feature on every compiler immediately
Some features, especially library ones, may depend on the standard library implementation.
Avoid this by
- checking compiler release notes
- checking library support tables
- testing with the actual toolchain you use
Comparisons
C++17 features compared with older approaches
| Concept | Before C++17 | C++17 approach | Why C++17 is nicer |
|---|---|---|---|
| Unpacking tuple/pair values | std::get<0>(x), first, second | Structured bindings | More readable names |
| Compile-time branching in templates | SFINAE, tag dispatch, specialization | if constexpr | Simpler template code |
| Optional value | sentinel values, pointers, custom flags | std::optional | Clearer meaning and safer API |
| One of several types | unions, inheritance, custom wrappers | std::variant | Type-safe alternative |
| Read-only string input | const std::string& or const char* | std::string_view | Flexible and efficient |
| Header-defined constants | separate definitions or tricky patterns | inline variables | Easier header-only code |
| File handling | platform-specific APIs or Boost.Filesystem | std::filesystem | Standard portable solution |
if vs if constexpr
| Feature | if | if constexpr |
|---|---|---|
| Decision time | Runtime | Compile time |
| Both branches must compile | Yes | No, discarded branch does not |
| Best for | Normal program flow | Templates and type-based logic |
std::optional vs pointers
| Feature | std::optional<T> | T* or nullptr |
|---|---|---|
| Expresses maybe-a-value | Yes | Sometimes |
| Ownership confusion | No | Possible |
| Good for return values | Yes | Usually less clear |
std::variant vs std::any
| Feature | std::variant | std::any |
|---|---|---|
| Allowed types known at compile time | Yes | No |
| Type safety | Stronger | More dynamic |
| Best use | Fixed set of possibilities | Arbitrary stored values |
Cheat Sheet
Quick reference to major C++17 features
Language features
- Structured bindings
auto [a, b] = pair_or_tuple; if constexprif constexpr (condition) { }- Fold expressions
(... + args) - Inline variables
inline constexpr int x = 10; - Nested namespaces
namespace a::b::c { } - Class template argument deduction
std::pair p(1, 2.0); - Attributes
[[nodiscard]] [[maybe_unused]] [[fallthrough]]
Library features
std::optional— value may be present or absentstd::variant— one of several known typesstd::any— value of any copyable typestd::string_view— non-owning string viewstd::filesystem— path and file operationsstd::byte— raw byte typestd::apply— call a function with tuple argumentsstd::invoke— generic callable invocationstd::clamp— limit a value to a rangestd::scoped_lock— lock multiple mutexes safely
Rules to remember
std::string_viewdoes not own data.std::optionalmust be checked before dereferencing.std::variantstores exactly one active alternative.if constexpris mainly useful in templates.C++1zwas the draft name for what became C++17.
Compiler note
Use:
-std=c++17
Older compilers may use:
-std=c++1z
FAQ
What does C++1z mean?
C++1z was the draft name for the standard that later became C++17. Compilers used it before the final name was official.
Are all C++1z features exactly the same as final C++17?
Usually many are the same, but draft support was sometimes incomplete or slightly different. Always verify feature support for your compiler and standard library.
What are the most useful C++17 features for beginners?
A good starting set is structured bindings, if constexpr, std::optional, std::variant, std::string_view, and std::filesystem.
Is std::filesystem part of C++17?
Yes. It became part of the standard library in C++17, although support details depended on the toolchain version.
Why is std::optional better than returning -1?
Because optional clearly expresses that a value may be missing, while -1 is just a convention and may be a valid value in some programs.
When should I use std::string_view?
Use it when you need read-only access to string data without owning or copying it, and when the original string data will stay alive long enough.
Is if constexpr the same as a normal if?
No. if constexpr performs compile-time branching and discards the non-selected branch during compilation.
Do I need a new compiler to use C++17?
You need a compiler and standard library with C++17 support. Language support and library support may arrive at different times.
Mini Project
Description
Build a small C++17 program that demonstrates several important C++17 features together in one practical example. The program will read a list of file-like names, classify them, and print results using modern syntax.
This project is useful because it combines common real-world patterns:
- returning maybe-a-value with
std::optional - storing multiple possible result types with
std::variant - iterating cleanly with structured bindings
- accepting text efficiently with
std::string_view
Even though the example is small, it mirrors the kinds of utilities developers write in scripts, tools, and backend services.
Goal
Create a C++17 program that analyzes file names, extracts an extension when present, and reports file information using modern C++17 features.
Requirements
- Accept several file names stored in a container.
- Write a function that returns a
std::optional<std::string>file extension. - Store file analysis results in a
std::variant. - Use structured bindings to iterate over named data.
- Print a readable summary for each file.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.