CPP17 Features
CPP17 Features
master
modern-cpp-features / CPP17.md
11 contributors
C++17
Overview
Many of these descriptions and examples are taken from various resources (see
Acknowledgements section) and summarized in my own words.
std::variant
std::optional
std::any
std::string_view
std::invoke
std::apply
std::filesystem
std::byte
splicing for maps and sets
parallel algorithms
* - For example, you cannot use a double as a template parameter type, which also makes
this an invalid deduction using auto .
Folding expressions
A fold expression performs a fold of a template parameter pack over a binary operator.
constexpr lambda
Compile-time lambdas using constexpr .
static_assert(addOne(1) == 2);
struct MyObj {
int value {123};
auto getValueCopy() {
return [*this] { return value; };
}
auto getValueRef() {
return [this] { return value; };
}
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321;
valueCopy(); // 123
valueRef(); // 321
Inline variables
The inline specifier can be applied to variables as well as to functions. A variable declared
inline has the same semantics as a function declared inline.
It can also be used to declare and define a static member variable, such that it does not
need to be initialized in the source file.
struct S {
S() : id{count++} {}
~S() { count--; }
int id;
static inline int count{0}; // declare and initialize count to 0 within the class
};
Nested namespaces
Using the namespace resolution operator to create nested namespace definitions.
namespace A {
namespace B {
namespace C {
int i;
}
}
}
namespace A::B::C {
int i;
}
Structured bindings
A proposal for de-structuring initialization, that would allow writing auto [ x, y, z ] =
expr; where the type of expr was a tuple-like object, whose elements would be bound to
the variables x , y , and z (which this construct declares). Tuple-like objects include
std::tuple , std::pair , std::array , and aggregate structures.
// Destructure by reference.
for (const auto& [key, value] : mapping) {
// Do something with key and value
}
Foo gadget(args);
switch (auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
// vs.
switch (Foo gadget(args); auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
constexpr if
Write code that is instantiated depending on a compile-time condition.
switch (n) {
case 1:
// ...
[[fallthrough]];
case 2:
// ...
break;
case 3:
// ...
[[fallthrough]];
default:
// ...
}
[[nodiscard]] issues a warning when either a function or class has this attribute and
its return value is discarded.
error_info do_something() {
error_info ei;
// ...
return ei;
}
__has_include
__has_include (operand) operator may be used in #if and #elif expressions to check
whether a header or source file ( operand ) is available for inclusion or not.
One use case of this would be using two libraries that work the same way, using the
backup/experimental one if the preferred one is not found on the system.
#ifdef __has_include
# if __has_include(<optional>)
# include <optional>
# define have_optional 1
# elif __has_include(<experimental/optional>)
# include <experimental/optional>
# define have_optional 1
# define experimental_optional
# else
# define have_optional 0
# endif
#endif
It can also be used to include headers existing under different names or locations on
various platforms, without knowing which platform the program is running on, OpenGL
headers are a good example for this which are located in OpenGL\ directory on macOS
and GL\ on other platforms.
#ifdef __has_include
# if __has_include(<OpenGL/gl.h>)
# include <OpenGL/gl.h>
# include <OpenGL/glu.h>
# elif __has_include(<GL/gl.h>)
# include <GL/gl.h>
# include <GL/glu.h>
# else
# error No suitable OpenGL headers found.
# endif
#endif
std::variant
The class template std::variant represents a type-safe union . An instance of
std::variant at any given time holds a value of one of its alternative types (it's also
possible for it to be valueless).
std::variant<int, double> v{ 12 };
std::get<int>(v); // == 12
std::get<0>(v); // == 12
v = 12.0;
std::get<double>(v); // == 12.0
std::get<1>(v); // == 12.0
std::optional
The class template std::optional manages an optional contained value, i.e. a value that
may or may not be present. A common use case for optional is the return value of a
function that may fail.
std::optional<std::string> create(bool b) {
if (b) {
return "Godzilla";
} else {
return {};
}
}
create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// optional-returning factory functions are usable as conditions of while and if
if (auto str = create(true)) {
// ...
}
std::any
A type-safe container for single values of any type.
std::any x {5};
x.has_value() // == true
std::any_cast<int>(x) // == 5
std::any_cast<int&>(x) = 10;
std::any_cast<int>(x) // == 10
std::string_view
A non-owning reference to a string. Useful for providing an abstraction on top of strings
(e.g. for parsing).
// Regular strings.
std::string_view cppstr {"foo"};
// Wide strings.
std::wstring_view wcstr_v {L"baz"};
// Character arrays.
char array[3] = {'b', 'a', 'r'};
std::string_view array_v(array, std::size(array));
std::apply
Invoke a Callable object with a tuple of arguments.
std::filesystem
The new std::filesystem library provides a standard way to manipulate files, directories,
and paths in a filesystem.
std::byte
The new std::byte type provides a standard way of representing data as a byte. Benefits
of using std::byte over char or unsigned char is that it is not a character type, and is
also not an arithmetic type; while the only operator overloads available are bitwise
operations.
std::byte a {0};
std::byte b {0xFF};
int i = std::to_integer<int>(b); // 0xFF
std::byte c = a & b;
int j = std::to_integer<int>(c); // 0
Note that std::byte is simply an enum, and braced initialization of enums become
possible thanks to direct-list-initialization of enums.
std::map<int, string> src {{1, "one"}, {2, "two"}, {3, "buckle my shoe"}};
std::map<int, string> dst {{3, "three"}};
dst.insert(src.extract(src.find(1))); // Cheap remove and insert of { 1, "one" } fro
dst.insert(src.extract(2)); // Cheap remove and insert of { 2, "two" } from `src` to
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };
auto elementFactory() {
std::set<...> s;
s.emplace(...);
return s.extract(s.begin());
}
s2.insert(elementFactory());
Parallel algorithms
Many of the STL algorithms, such as the copy , find and sort methods, started to
support the parallel execution policies: seq , par and par_unseq which translate to
"sequentially", "parallel" and "parallel unsequenced".
std::vector<int> longVector;
// Find element using parallel execution policy
auto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longV
// Sort elements using sequential execution policy
auto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longV
Acknowledgements
Author
Anthony Calandra
Content Contributors
See: https://github.com/AnthonyCalandra/modern-cpp-features/graphs/contributors
License
MIT