C++17 New Features: Complete Guide and Reference

C++17 was a major update introducing many new features. This guide covers all C++17 features with examples.

Core Language Features

1. Structured Bindings

Decompose objects into individual variables.

#include <tuple>
#include <map>

// Tuple decomposition
std::tuple<int, double, std::string> t(42, 3.14, "hello");
auto [a, b, c] = t;  // a=42, b=3.14, c="hello"

// With references
auto& [x, y, z] = t;  // References to tuple elements

// Array decomposition
int arr[] = {1, 2, 3};
auto [first, second, third] = arr;

// Pair decomposition
std::pair<int, std::string> p(10, "test");
auto [key, value] = p;

// Map iteration

std::map<std::string, int> m = {{"one", 1}, {"two", 2}};
for (const auto& [key, val] : m) {
    std::cout << key << ": " << val << std::endl;
}


// Struct decomposition
struct Point {
    int x, y;
};

Point pt{10, 20};
auto [x, y] = pt;  // x=10, y=20

2. if constexpr

Compile-time conditional compilation.

template<typename T>
auto process(T value) {
    if constexpr (std::is_integral_v<T>) {
        return value * 2;
    } else if constexpr (std::is_floating_point_v<T>) {
        return value / 2.0;
    } else {
        return value;
    }
}

int result1 = process(10);      // 20
double result2 = process(10.0); // 5.0

// No runtime overhead - evaluated at compile time
template<typename T>
void print_type() {
    if constexpr (std::is_same_v<T, int>) {
        std::cout << "Integer" << std::endl;
    } else if constexpr (std::is_same_v<T, double>) {
        std::cout << "Double" << std::endl;
    }
}

3. Fold Expressions

Simplify variadic template expansion.

// C++11: complex recursive template
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // C++17: unary right fold
}

int result = sum(1, 2, 3, 4, 5);  // 15

// Left fold
template<typename... Args>
auto product(Args... args) {
    return (... * args);  // Left fold
}

// Binary folds
template<typename... Args>
bool all_true(Args... args) {
    return (args && ...);  // AND fold
}

template<typename... Args>
bool any_true(Args... args) {
    return (args || ...);  // OR fold
}

// With initial value
template<typename... Args>
auto sum_with_init(int init, Args... args) {
    return (init + ... + args);  // Binary left fold
}

// Print all arguments
template<typename... Args>
void print_all(Args... args) {
    ((std::cout << args << " "), ...);  // Comma fold
    std::cout << std::endl;
}

print_all(1, 2.5, "hello", 'c');  // 1 2.5 hello c

4. inline Variables

Define variables in headers without ODR violations.

// C++17: inline variables
// In header file
inline int global_counter = 0;
inline const std::string app_name = "MyApp";

// Inline static member
class MyClass {
public:
    inline static int count = 0;  // Can define in class
    static constexpr double pi = 3.14159;  // Still works
};

// No need for separate definition in .cpp file

5. Nested Namespace Definitions

Simplified nested namespace syntax.

// Before C++17
namespace A {
    namespace B {
        namespace C {
            int value = 42;
        }
    }
}

// C++17: nested namespace
namespace A::B::C {
    int value = 42;
}

// Usage
int x = A::B::C::value;

6. __has_include Preprocessor

Check if header is available.

#if __has_include(<optional>)
    #include <optional>
    #define HAS_OPTIONAL 1
#else
    #define HAS_OPTIONAL 0
#endif

#if __has_include("custom_header.h")
    #include "custom_header.h"
#endif

7. [[fallthrough]] Attribute

Mark intentional fallthrough in switch.

switch (value) {
    case 1:
        do_something();
        [[fallthrough]];  // Intentional fallthrough
    case 2:
        do_something_else();
        break;
    case 3:
        // Compiler warning without [[fallthrough]]
        do_another_thing();
        break;
}

8. [[nodiscard]] Attribute

Warn if return value is ignored.

[[nodiscard]] int compute_important_value() {
    return 42;
}

void func() {
    compute_important_value();  // Warning: return value discarded
    auto x = compute_important_value();  // OK
}

// Useful for error codes
[[nodiscard]] bool allocate_memory(size_t size) {
    // ...
    return success;
}

9. [[maybe_unused]] Attribute

Suppress unused variable warnings.

void func(int x, [[maybe_unused]] int y) {
    // y might not be used in all code paths
    if (x > 0) {
        std::cout << y << std::endl;
    }
}

[[maybe_unused]] static int debug_counter = 0;

10. Guaranteed Copy Elision

Copy elision is mandatory in some cases.

struct NonCopyable {
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
};

NonCopyable func() {
    return NonCopyable{};  // C++17: guaranteed no copy
}

auto obj = func();  // Direct construction, no copy

11. Template Argument Deduction for Class Templates

Deduce template arguments from constructor.

// C++17: class template argument deduction
std::pair p(1, 2.0);           // std::pair<int, double>
std::tuple t(1, 2.0, "hello"); // std::tuple<int, double, const char*>
std::vector v{1, 2, 3, 4, 5};  // std::vector<int>

// Works with custom types
template<typename T>
class Container {
public:
    Container(T value) : value_(value) {}
private:
    T value_;
};

Container c(42);  // Container<int>

12. if and switch with Initializer

Initialize variables in condition.

// if with initializer
if (auto it = map.find(key); it != map.end()) {
    // it is available in this scope
    std::cout << it->second << std::endl;
}
// it is out of scope here

// switch with initializer
switch (auto status = get_status(); status) {
    case Status::Ok:
        // ...
        break;
    case Status::Error:
        // ...
        break;
}

// Prevents variable leakage
if (std::lock_guard<std::mutex> lock(mtx); condition) {
    // lock is held here
}

Standard Library

13. std::optional

Represent optional values.

#include <optional>

std::optional<int> find_value(int key) {
    if (key == 42) {
        return 100;
    }
    return std::nullopt;  // No value
}

auto result = find_value(42);
if (result.has_value()) {
    std::cout << *result << std::endl;  // 100
}

// Or use value_or
int value = result.value_or(0);  // 100 if present, 0 otherwise

// Direct access
std::optional<std::string> name = "John";
std::cout << name.value() << std::endl;  // "John"

// With structured bindings
if (auto opt = find_value(42); opt.has_value()) {
    std::cout << *opt << std::endl;
}

14. std::variant

Type-safe union.

#include <variant>

std::variant<int, double, std::string> v;
v = 42;              // Holds int
v = 3.14;            // Holds double
v = "hello";         // Holds string

// Access with std::get
int i = std::get<int>(v);
double d = std::get<double>(v);

// Safe access with std::get_if
if (auto* ptr = std::get_if<int>(&v)) {
    std::cout << *ptr << std::endl;
}

// Visit pattern
std::visit([](auto& arg) {
    std::cout << arg << std::endl;
}, v);

// Index-based access
std::cout << v.index() << std::endl;  // 0, 1, or 2

15. std::any

Type-erased value container.

#include <any>

std::any a = 42;
a = 3.14;
a = std::string("hello");

// Type checking
if (a.type() == typeid(int)) {
    int value = std::any_cast<int>(a);
}

// Safe casting
try {
    int value = std::any_cast<int>(a);
} catch (const std::bad_any_cast& e) {
    // Wrong type
}

// Pointer casting (no exception)
if (int* ptr = std::any_cast<int>(&a)) {
    std::cout << *ptr << std::endl;
}

16. std::string_view

Non-owning string reference.

#include <string_view>

std::string str = "Hello, World!";
std::string_view sv = str;  // No copy
std::string_view sv2 = "Literal";  // Points to literal

// Substring without copy
std::string_view substr = sv.substr(0, 5);  // "Hello"

// Works with C strings
const char* cstr = "Test";
std::string_view sv3(cstr);

// STL algorithms
std::string_view text = "hello world";
auto pos = text.find("world");

// No allocation overhead
void process(std::string_view sv) {
    // Efficient - no string copy
}

17. std::filesystem

Filesystem operations.

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

// Path operations
fs::path p = "/usr/bin";
p /= "program";  // /usr/bin/program
std::cout << p.string() << std::endl;

// Check existence
if (fs::exists(p)) {
    std::cout << "Exists" << std::endl;
}

// File operations
if (fs::is_regular_file(p)) {
    std::cout << "Regular file" << std::endl;
}
if (fs::is_directory(p)) {
    std::cout << "Directory" << std::endl;
}

// Iterate directory
for (const auto& entry : fs::directory_iterator("/tmp")) {
    std::cout << entry.path() << std::endl;
}

// Recursive iteration
for (const auto& entry : fs::recursive_directory_iterator("/usr")) {
    std::cout << entry.path() << std::endl;
}

// File size
auto size = fs::file_size("file.txt");

// Copy, move, remove
fs::copy("source.txt", "dest.txt");
fs::rename("old.txt", "new.txt");
fs::remove("file.txt");
fs::remove_all("directory");

18. Parallel Algorithms

Parallel execution of STL algorithms.

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

std::vector<int> vec(1000000, 1);

// Sequential
std::sort(vec.begin(), vec.end());

// Parallel
std::sort(std::execution::par, vec.begin(), vec.end());

// Parallel unsequenced (most aggressive)
std::sort(std::execution::par_unseq, vec.begin(), vec.end());

// Parallel policies
std::for_each(std::execution::par, vec.begin(), vec.end(), [](int& n) {
    n *= 2;
});

// Reduce
int sum = std::reduce(std::execution::par, vec.begin(), vec.end());

// Transform reduce
int result = std::transform_reduce(
    std::execution::par,
    vec1.begin(), vec1.end(),
    vec2.begin(),
    0
);

19. std::invoke

Invoke callable objects uniformly.

#include <functional>

void func(int x) { std::cout << x << std::endl; }

struct Functor {
    void operator()(int x) { std::cout << x << std::endl; }
};

// Uniform invocation
std::invoke(func, 42);
std::invoke(Functor{}, 42);
std::invoke([](int x) { std::cout << x << std::endl; }, 42);

// Member function
struct MyClass {
    void method(int x) { std::cout << x << std::endl; }
};

MyClass obj;
std::invoke(&MyClass::method, obj, 42);

20. Mathematical Special Functions

#include <cmath>

// Beta function
double beta = std::beta(2.0, 3.0);

// Bessel functions
double j0 = std::cyl_bessel_j(0, 1.0);

// Legendre polynomials
double p = std::legendre(3, 0.5);

// And many more...

21. std::byte

Type-safe byte representation.

#include <cstddef>

std::byte b{0x42};
std::byte b2 = std::byte{0xFF};

// Bitwise operations
std::byte result = b | b2;
result = b & b2;
result = ~b;
result = b << 2;

// Conversion
unsigned char uc = static_cast<unsigned char>(b);
std::byte b3 = std::byte{uc};

22. std::scoped_lock

RAII lock for multiple mutexes.

#include <mutex>

std::mutex mtx1, mtx2;

// C++17: scoped_lock (replaces lock_guard for multiple mutexes)
{
    std::scoped_lock lock(mtx1, mtx2);  // Locks both, deadlock-safe
    // Critical section
}  // Automatically unlocks both

// Better than manual locking
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> l1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> l2(mtx2, std::adopt_lock);

23. std::shared_mutex (non-timed)

Read-write lock without timeout.

#include <shared_mutex>

std::shared_mutex mtx;
int shared_data = 0;

// Writer
void writer() {
    std::unique_lock<std::shared_mutex> lock(mtx);
    shared_data = 42;
}

// Reader
void reader() {
    std::shared_lock<std::shared_mutex> lock(mtx);
    int value = shared_data;  // Multiple readers allowed
}

Other Features

24. Constructor Template Argument Deduction Guides

template<typename T>
class Container {
public:
    Container(T value) : value_(value) {}
private:
    T value_;
};

// Deduction guide
template<typename T>
Container(T) -> Container<T>;

Container c(42);  // Container<int>

25. constexpr if in Templates

template<typename T>
auto process(T value) {
    if constexpr (std::is_integral_v<T>) {
        return value * 2;
    } else {
        return value;
    }
}

Summary Table

Feature Category Description
Structured bindings Language Decompose objects
if constexpr Language Compile-time if
Fold expressions Language Variadic template simplification
inline variables Language Header-safe variables
Nested namespaces Language A::B::C syntax
[[nodiscard]] Language Warn on ignored return
[[fallthrough]] Language Switch fallthrough
Template deduction Language Class template argument deduction
std::optional Library Optional values
std::variant Library Type-safe union
std::any Library Type-erased container
std::string_view Library Non-owning string
std::filesystem Library Filesystem operations
Parallel algorithms Library Parallel STL
std::byte Library Type-safe byte

Migration Tips

  1. Use std::optional instead of sentinel values
  2. Use std::string_view for function parameters
  3. Use structured bindings for pairs/tuples
  4. Use if constexpr for template metaprogramming
  5. Use fold expressions for variadic templates
  6. Use std::filesystem instead of platform-specific APIs

Compiler Support

  • GCC: 7.0+ (full support)
  • Clang: 5.0+ (full support)
  • MSVC: Visual Studio 2017 15.3+ (full support)

Compile with: -std=c++17 (GCC/Clang) or /std:c++17 (MSVC)


C++17 was a major update that significantly improved expressiveness, type safety, and performance of modern C++.