Skip to content
C++
C++17
Published ISO/IEC 14882:2017
Widely deployed

C++17

A large, practical release. Structured bindings clean up loop bodies. if constexpr replaces SFINAE in function templates. std::variant, std::optional, and std::any bring sum types and nullable values to the standard library. std::filesystem ends platform-specific path hell.

Structured bindings
if constexpr
CTAD
std::variant
std::optional
std::filesystem
Parallel algorithms

Core Language

Structured bindings

Unpack tuples, pairs, structs, and arrays into named variables. Cleaner loop bodies, cleaner returns.

#include <map>
#include <tuple>

// Unpack pair from map iteration
std::map<std::string, int> scores{{"Alice", 95}, {"Bob", 87}};
for (const auto& [name, score] : scores) {
    std::cout << name << ": " << score << '\n';
}

// Unpack tuple
auto [x, y, z] = std::make_tuple(1, 2.0, "three");

// Unpack struct
struct Point { int x, y; };
Point p{3, 4};
auto [px, py] = p;
if constexpr

Compile-time if that discards the not-taken branch entirely. Replaces enable_if and SFINAE for template specialization in function bodies.

template<typename T>
auto process(T val) {
    if constexpr (std::is_integral_v<T>) {
        return val * 2;        // only compiled for integral types
    } else if constexpr (std::is_floating_point_v<T>) {
        return val * 2.0;      // only compiled for floating-point types
    } else {
        static_assert(false, "unsupported type");
    }
}

process(5);    // returns int 10
process(2.5);  // returns double 5.0
Class Template Argument Deduction (CTAD)

Deduce class template arguments from constructor parameters. No more make_pair, make_tuple, make_unique boilerplate.

#include <vector>
#include <pair>
#include <tuple>

// C++14 — had to specify or use make_*
std::pair<int, std::string> p14{1, "hello"};

// C++17 — deduced from constructor
std::pair p17{1, "hello"};       // pair<int, const char*>
std::vector v{1, 2, 3, 4, 5};   // vector<int>
std::tuple t{1, 2.0, "three"};  // tuple<int, double, const char*>
Fold expressions

Apply a binary operator over a variadic parameter pack. Replaces recursive template instantiation for variadic operations.

template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // left fold: ((a + b) + c) + ...
}

template<typename... Args>
bool all_positive(Args... args) {
    return ((args > 0) && ...);  // fold with &&
}

sum(1, 2, 3, 4, 5);          // 15
all_positive(1, 2, -3);      // false
Inline variables

Variables in headers without multiple-definition errors. Essential for constexpr inline constants and header-only libraries.

// constants.h — safe to include in multiple TUs
inline constexpr double PI = 3.141592653589793;
inline constexpr int MAX_RETRIES = 3;

// C++14 required workarounds like:
// static constexpr double PI = 3.14; // or extern + .cpp definition

Library

std::variant

Type-safe union. Knows which alternative is active. std::visit provides exhaustive pattern matching.

#include <variant>

using Shape = std::variant<Circle, Rectangle, Triangle>;

double area(const Shape& s) {
    return std::visit([](const auto& shape) {
        return shape.area();
    }, s);
}

Shape s = Circle{5.0};
double a = area(s);  // 78.54...

// holds_alternative, get, get_if
if (std::holds_alternative<Circle>(s)) {
    auto& c = std::get<Circle>(s);
}
std::optional<T>

An optional value — either T or nothing. Replaces null pointers, sentinel values, and bool+output-param patterns.

#include <optional>

std::optional<int> find_index(
    const std::vector<int>& v, int target)
{
    auto it = std::ranges::find(v, target);
    if (it == v.end()) return std::nullopt;
    return std::distance(v.begin(), it);
}

auto idx = find_index(v, 42);
if (idx) {
    std::cout << "found at " << *idx << '\n';
}
int pos = idx.value_or(-1);
std::filesystem

Cross-platform file system operations: paths, directory iteration, file info, copy, rename, create_directories.

#include <filesystem>
namespace fs = std::filesystem;

// Path operations
fs::path p = "/home/user/file.txt";
p.filename();   // "file.txt"
p.stem();       // "file"
p.extension();  // ".txt"
p.parent_path(); // "/home/user"

// Directory iteration
for (const auto& entry : fs::directory_iterator(".")) {
    if (entry.is_regular_file()) {
        std::cout << entry.path().filename()
                  << " " << entry.file_size() << " bytes\n";
    }
}

fs::create_directories("a/b/c");
fs::copy("src.txt", "dst.txt");
std::string_view

Non-owning, read-only view over a character sequence. Pass string data without copying, without owning, without null-terminator requirement.

#include <string_view>

// Takes any string-like without allocation
void log(std::string_view msg) {
    std::cout << msg;  // works with string, literal, span<char>
}

log("hello");                         // literal — no allocation
log(std::string{"world"});            // string — no copy
log(std::string_view{buf, buf_len}); // raw buffer — no copy

// Efficient substr — returns another view, no allocation
std::string_view sv = "hello world";
auto hello = sv.substr(0, 5);  // "hello" — no copy
Parallel algorithms

std::execution_policy controls parallel execution of STL algorithms. std::par, std::par_unseq for CPU parallelism.

#include <algorithm>
#include <execution>
#include <vector>

std::vector<int> v(1'000'000);
std::iota(v.begin(), v.end(), 0);

// Parallel sort — uses all CPU cores
std::sort(std::execution::par, v.begin(), v.end());

// Parallel + vectorized transform
std::transform(std::execution::par_unseq,
    v.begin(), v.end(), v.begin(),
    [](int x) { return x * 2; });