Skip to content

Last updated:2026-10-12

C++17 Seventeen Advanced Features

A complete rundown of 17 advanced C++17 features

C++17 is one of the most commonly used versions today. This time I spent some effort sorting out 17 important features - and there are more features than just these.

1. Parallel algorithms

C++17 introduced parallel versions of many algorithms in the standard library. These algorithms can execute in parallel, so they may bring significant performance improvements on multi-core systems.

I previously wrote an article that covers this feature in depth; you can read that one:

Example:

#include <algorithm>
#include <vector>
#include <execution>
int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::sort(std::execution::par, v.begin(), v.end());
}

In this example, std::sort executes in parallel, sorting the elements of the vector v in a parallel fashion.

2. If Initializers

The if initializer in C++17 is a feature that allows variables to be initialized directly in an if statement. This style of initialization can, to some extent, improve the readability and conciseness of the code.

In traditional C++, we would usually initialize the variable like this:

int x;
if (condition) {
    x = 42;
} else {
    x = 24;
}

In C++17, however, an if initializer can be used to simplify this process and make the code more compact:

if (bool condition = /* some condition */) {
    int x = 42;
} else {
    int x = 24;
}

In this example, we put the initialization of the variable x directly in the if statement. The variable condition is defined and initialized in the if statement and is then usable inside the if block. This style is more intuitive and concise, especially for simple conditional initialization.

3. Class Template Argument Deduction (CTAD)

CTAD lets the compiler deduce template arguments automatically from function arguments. This makes templates easier to use without having to specify template arguments explicitly.

A previous article covered this feature in depth:

Example:

template <typename T>
void foo(T t) {
    // ...
}

int main() {
    foo(42);  // 编译器推导出T的类型为int
}

In this example, when foo(42) is called, the compiler deduces that the type of T is int.

4. template <auto>

The template keyword was introduced as a placeholder for non-type template parameters. It allows values of any type to be represented in a template.

Example:

template<auto value>
struct constant {
    static constexpr auto get_value() { return value; }
};

// 用法
static_assert(constant<42>::get_value() == 42);

5. std::optional and std::variant

std::optional and std::variant are two new types introduced in C++17. std::optional represents a value that may or may not exist, while std::variant is a type-safe union that can hold values of different types.

Example:

#include <optional>
#include <variant>

int main() {
    std::optional<int> opt = 42;
    std::variant<int, double> var = 3.14;
}

In this example, opt is an optional integer holding the value 42, and var is a variant holding the value 3.14.

6. Fold expressions

In C++17, fold expressions provide a concise way to apply binary operations to a parameter pack. They allow operations such as summing, multiplying, or concatenating the elements of a parameter pack without explicit recursion or iteration.

For example:

#include <iostream>

template<typename T>
T sum(T t) {
    return t;
}

// 使用折叠表达式的递归情况
template<typename T, typename... Args>
T sum(T first, Args... args) {
    return first + sum(args...);
}

int main() {
    int total = sum(1, 2, 3, 4, 5);
    std::cout << "总和: " << total << std::endl;

    return 0;
}

The fold expression (first + ... + args) in the recursive sum function applies the addition operation to every element of the parameter pack.

7. Structured bindings

Structured bindings allow you to decompose an object into its constituent elements, similar to the tuple unpacking you may already be familiar with.

Previous article:

Example:

#include <tuple>
#include <string>
int main() {
    std::tuple<int, std::string, double> t(42, "hello", 3.14);
    auto [i, s, d] = t;  // i = 42, s = "hello", d = 3.14
}

In this example, the structured binding [i, s, d] decomposes the tuple t into its constituent elements.

8. Template template parameters

For example: in C++17, the syntax template<template<class...>typename bob> struct foo {} declares a template named foo that accepts a template template parameter named bob. The template template parameter bob itself accepts any number of template type parameters.

template <template<class...> typename bob>
struct foo {
    template<typename T>
    void bar(const bob<T>& arg) {
        std::cout << "size: " << arg.size() << std::endl;
    }
};

int main() {
    foo<std::vector> f;
    std::vector<int> vec = {1, 2, 3, 4, 5};
    f.bar(vec);

    return 0;
}

In the main function, we instantiate foo with std::vector as the template argument for bob. This lets us create a generic structure foo that can work with any template accepting any number of type parameters, such as std::vector, std::list, or user-defined templates.

9. Inline variables

C++17 allows variables to be defined as inline inside the class definition, which can help reduce binary size and may improve performance by preventing duplicate copies of the variable across multiple translation units.

Example:

class MyClass {
public:
    inline static int inlineVar = 42;
};

int main() {
    int localVar = MyClass::inlineVar;
}

Here, inlineVar is an inline static member variable of MyClass.

10. Attribute improvements

C++17 provides new attributes and improves existing ones, allowing developers to give the compiler more information about how the code behaves.

Example:

[[nodiscard]] int get_sum(int a, int b) {
    return a + b;
}

int main() {
    auto result = get_sum(1, 2);
    // 编译器可能会警告‘result’未使用
}

In this example, [[nodiscard]] is an attribute that can be applied to a function, indicating that its return value should not be discarded by the caller.

11. Nested namespaces

C++17 enhanced variadic templates with fold expressions, making code that handles parameter packs more concise and expressive.

Example:

// 外部命名空间
namespace outer {
    // 内部命名空间
    namespace inner {
        void foo() {
            std::cout << "在内部命名空间中" << std::endl;
        }
    }
}

outer::inner::foo();

Nested namespace definitions provide a way to organize code hierarchically, improving readability and maintainability, especially in large projects. They also help avoid naming conflicts by providing a more structured namespace hierarchy.

12. Literal improvements

C++17 enhanced literals, including improvements to integer and floating-point literals, as well as support for the true and false literals.

Example:

auto num = 123_456; // Underscore in integer literals  
auto pi = 3.1415_f; // Suffix for floating-point literals

13. constexpr Lambda

C++17 allows lambda functions to be constexpr; if they satisfy the requirements, they can be used in contexts that require compile-time evaluation, for example:

constexpr auto lambda = [](int x) { return x * 2; };
static_assert(lambda(5) == 10);

In this example, lambda is a constexpr lambda that takes an integer x as its parameter and returns twice x. The static_assert checks at compile time whether the value of lambda(5) equals 10.

14. Capturing *this

Capturing *this in a lambda has become simpler, allowing direct access to the members of the containing object.

class Foo {
    int data = 42;
public:
    auto member_lambda() {
        return [*this] { std::cout << data << std::endl; };
    }
};

// 使用
Foo f;
f.member_lambda()(); // 输出 "42"

15. Extended if and switch statements

The condition of an if or switch statement can now be any expression, not just a boolean condition. This makes control flow more flexible, for example when using structured bindings:

if (const auto [it, inserted] = map.insert({"foo", bar}); inserted) {
    // ...
}

In this example, the if statement checks whether the variable inserted is true, but the condition also includes the assignment of a structured binding.

16. Generalized range-based for loops

This improvement supports sentinels, or end iterators, whose type differs from the begin iterator, which helps when dealing with null-terminated loops and other similar cases. For example:

for (auto it = my_container.begin(); it != my_container.end(); ++it) {
   // ...
}

In this example, my_container might be a container that uses a different type for its end iterator, but the loop still works correctly.

17. if constexpr

This feature allows the compiler to evaluate conditions at compile time, enabling more generic code. If the condition is true, the code inside the if block is included in the compiled code; otherwise it is discarded. This can simplify code by eliminating unnecessary branches at run time. For example:

if constexpr (std::is_same_v<T, int>) {
   // 用于int的特定代码
} else {
   // 用于其他类型的代码
}

In this example, the if constexpr statement checks whether the type T is int and includes the appropriate code accordingly.

Summary

That is all for this section; the remaining content will be covered in the next one.

Comments