C++17 New Features: Complete Guide and Reference
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
- Use
std::optionalinstead of sentinel values - Use
std::string_viewfor function parameters - Use structured bindings for pairs/tuples
- Use
if constexprfor template metaprogramming - Use fold expressions for variadic templates
- Use
std::filesysteminstead 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++.