Supplement to this video. Watch first 35 min (the language bits), then 1:14:30 - 1:18:15 (the new map features).
You can find a fairly comprehensive list of new features on cppreference or here if you really want to see it all.
Recap of features described in the video
Structured bindings
auto [promise, future] = makePromiseFuture<int>();
for (auto&& [key, val] : someMap) { /* ... */ }
Automatically works with all-public structs, arrays, std::pair
and
std::tuple
. Other types may be supported if they supply a tuple-like
protocol (see docs for details).
if
and switch
with initializers
if (auto status = doThing(); !status.isOK())
return status;
if (auto num = parsNumberFromString<int>(str); !num.isOK()) {
return num.getStatus();
} else {
return doThing(num.getValue());
}
switch (auto res = inputDocSource->getNext(); res.getStatus()) {
case kEOF:
case kPauseExecution:
return res;
case kAdvanced:
return process(res.releaseDocument());
}
constexpr
if
template <typename RetType, typename F, typename... Args>
RetType call(F&& func, Args... args) {
if constexpr (std::is_same<RetType, void>()) {
func(args...);
} else {
return func(args...);
}
}
Yes, the feature is called "constexpr if" but spelled if constexpr
...
Fold expressions
template <typename Nums>
auto sum(Nums... nums) {
return (0 + ... + nums);
}
You will generally want to use "left folds" which are the forms which put the
...
before the pack to be expanded.
Class Template Argument Deduction (CTAD)
auto ints = std::vector{1,2,3};
auto lk = std::unique_lock(someMutex);
// used to need std::unique_lock<std::mutex>(someMutex)
std::unique_ptr<SomeType> factory();
auto shared = std::shared_ptr(factory());
// Deduction guides let types customize the behavior:
template <typename T>
MyContainer(const std::vector<T>&) -> MyContainer<T>;
template <typename It>
MyContainer(It begin, It end) -> MyContainer<typename std::iterator_traits<It>::value_type>;
auto
in non-type template parameters
template <auto CodeOrCategory>
using ExceptionFor = /* ... */;
try {
/*...*/
} catch(ExceptionFor<ErrorCodes::HostNotFound>& ex) {
/*...*/
} catch(ExceptionFor<ErrorCategory::NotMasterError>& ex) {
// Used to need ExceptionForCat<...> here.
}
inline
variables
class Classy {
inline static int count = 0;
// Used to need to define separately in a cpp file.
// static constexpr members are implicitly inline.
static constexpr int kNum = 42;
static constexpr StringData kName = "Bob"_sd;
};
constexpr
lambdas
constexpr auto example = [] () constexpr {};
// constexpr on lambdas' call operators is automatic if it is legal.
constexpr auto frobnicate = [] (int i) { return i * 42; };
constexpr auto res = frobnicate(21);
static_assert
Unary static_assert(some_condition());
// used to require a second string argument.
// We use MONGO_STATIC_ASSERT(cond) to get this now.
Guaranteed copy elision
auto lk = std::lock_guard(mutex);
UnmovableType func() {
return UnmovableType(args);
// This still isn't legal (no NRVO):
UnmoveableType out;
out.doThing();
return out;
}
auto res = func();
Prefer using that style of declaration in C++17 over std::lock_guard lk(mx);
as it is more visually distinct from std::lock_guard(mx);
and std::lock_guard mx;
so you are less likely to accidentally type them, and they will stand out more in
reviews.
Nested namespace definitions
namespace mongo::repl {
/*...*/
} // namespace mongo::repl
map
and unordered_map
New APIs for // Replaces val if key already present:
auto [it, inserted] = myMap.insert_or_assign(key, val);
// Does nothing (not even construct the value) if key is present:
auto [it, inserted] = myMap.try_emplace(key, args_to_value_constructor...);
map<string, vector<int>>().try_emplace("") // value is empty vector.
map<string, vector<int>>().try_emplace("", 5) // value is vector with 5 zeros.
map<string, vector<int>>().try_emplace("", 5, 1) // value is vector with 5 ones.
map<string, pair<int, string>>().try_emplace("", 5, "one") // value is pair(5, "one").
// Extract node from map:
auto node = map.extract(keyOrIterator);
node.key().mutate(); // key is mutable when outside of any map.
otherMap.insert(std::move(node));
// Steals all nodes from source that don't have equivalent key in map:
map.merge(source);
Docs:
insert_or_assign
try_emplace
extract
merge
Library bits skipped in the video
std::string_view
is basically mongo::StringData
optional
, variant
and any
pulled from Boost to std::
boost::filesystem
standardized as std::filesystem
Polymorphic Memory Resources (PMR) Allocators
Parallel STL algorithms and execution policies
Useful lib features not covered
C++17 added a lot of lib extensions, but I think only a few will be useful to most of us.
to_chars()
and from_chars()
Finally a locale-independent and (hopefully) fast way to convert numbers to and
from strings. Explicitly designed for things like JSON parsing/serialization. It
has a somewhat complicated, low-level API to avoid requiring allocating or any
other potentially expensive operations. We may want to replace the
implementation of parseNumberFromString()
with a call to from_chars()
.
invoke()
and friends
invoke(f, args...)
performs the operation the standard has used the magic INVOKE
token for, but
never provided a way to use. It calls f
passing args...
, but in addition to
normal functions and function-objects, it also supports pointer-to-members
(&type::member
) and pointer-to-member-functions (&type::method
). For them,
it behaves like the implicit this
parameter added as an explicit first
parameter, and it supports passing both references and pointers to the object.
Also:
is_invocable<F, Args...>
andinvocation_result_t<F, Args...>
do what they look likeis_invocable_r<RetType, F, Args...>
to test if the return type ofF(Args...)
is compatible withRetType
. Everything is compatible withvoid
.
apply(f, tuple)
is likeinvoke
but splats the tuple into argumentsmake_from_tuple<T>(tuple)
is likeapply
but constructs aT
object
scoped_lock
Variadic lock guard called Like a mix of lock_guard
with
lock()
to let you acquire
multiple locks while avoiding deadlocks. Not movable like unique_lock
.
// Two functions with these lines are guaranteed *not* to deadlock:
auto lk = std::scoped_lock(mx1, mx2);
auto lk = std::scoped_lock(mx2, mx1);
// Can also be used just like plain old lock_guard:
auto lk = std::scoped_lock(mx);
Grab bag of small stuff
boyer_moore_searcher
Sub-linear needle-in-haystack string search algorithmclamp(val, min, max)
returnsval
restricted to betweenmin
andmax
shared_ptr<T[]>
array supportweak_from_this()
likeshared_from_this()
as_const(val)
returns a const-ref to val- Proposed by ADAM
Language features not covered
In rough order most useful to least.
Improved order of evaluation rules
This is really useful, but hard to demo because it basically just makes code do what we expect when reading it.
Example from the standard:
std::string s = "but I have heard it works even if you don’t believe in it";
s.replace(0, 4, "")
.replace(s.find("even"), 4, "only")
.replace(s.find(" don’t"), 6, " ");
assert(s == "I have heard it works only if you believe in it"); // Now OK
This example was included in Bjarne Stroustrup's book The C++ Programming Language, 4th Edition, which was reviewed by several experts but nobody noticed this bug. It actually produces different output on different compilers.
These are the additional rules. All examples use letter order to show evaluation order. Anything indeterminately sequenced will use the same letters.
- Using an overloaded operator keeps the same order as builtins (
A, B
even if the comma operator is overloaded, but still don't do that!) - Function objects evaluated before arguments (
(A(B))(C)
) - Function arguments are still evaluated in arbitrary order, but now at least
each argument is fully evaluated before moving on to the next (
A(C(B, B), D)
orA(D(C, C), B)
, but notA(D(B, B), C)
- This means that
f(unique_ptr<int>(new int()), may_throw())
is no longer a potential leak. Item 17 from Effective C++ can now be forgotten!
- This means that
- Assignment evaluates RHS before LHS (
B = A
andB += A
)- This prevents leaving an unwanted element in code like
my_map[key] = may_throw()
- This prevents leaving an unwanted element in code like
- Object before member access, including methods and pointer-to-member usages
(
A.B(C)
,A->B(C)
,(A.*B)(C)
,(A->*B)(C)
)- This makes chaining/fluent APIs work as intended
- Container before subscripts (
A[B]
) - Shifts (AKA stream-to and stream-from) are left to right (
A << B << C
andA >> B >> C
andA >> B << C
)
You can find the full list of rules (old and new) here.
*this
by value
Lambda capture of [this] { method()}; // Captures the object by pointer/reference
[self = *this] { self.method(); } // C++14 capture by copy
[*this] { method(); } // C++17 capture by copy
[self = std::move(*this)] { self.method(); } // Still only way to capture by move
Types with public non-virtual bases can be aggregates
It behaves as-if the bases were initial members, recursively. This lets you avoid writing constructors for simple types.
struct Person { std::string name; };
struct Employee : Person { int id = 0; };
auto person = Person{"John Doe"}; // Valid in C++14
auto employee = Employee{"Bob Smith", 1234}; // Now also valid
New attributes
[[nodiscard]]
- On functions, issues a warning if the return value is "unused". On types, acts as if all functions that return the type were marked[[nodiscard]]
- Standardization of non-std attribute we already use on
Status
,StatusWith
, andFuture
- The standard is intentionally vague about what "unused" means to allow compilers freedom to choose their own hueristics, and most seem to do a good job
- Casting-to-void is defined to suppress the warning (
(void)dont_ignore_me();
)
[[nodiscard]] bool returnsFalseOnFailure(); struct [[nodiscard]] Status { /*...*/ }; Status doThing(); void func() { returnsFalseOnFailure(); // Warning! doThing(); // Warning! (void)doThing(); // no warning, result intentionally ignored. }
- Standardization of non-std attribute we already use on
[[fallthrough]]
- Suppress warnings about implicit fallthrough in switch statementsswitch(var) { case A: do_a_stuff(); [[fallthrough]]; // no warning case B: do_a_and_b_stuff(); // forgot break; - warning on recent compilers case D: case C: // No warning if no code between cases. do_c_and_d_stuff(); }
[[maybe_unused]]
- Suppress warnings about unused entities. Most useful for variables only used in some build modes.[[maybe_unused]] const int orig_size = obj.size(); // Add and remove items from obj... #ifdef DEBUG assert(obj.size() == orig_size); #endif
noexcept
now part of the type system
using MayThrow = void();
using NoThrow = void() noexcept;
void mayThrow();
void noThrow() noexcept;
MayThrow* a = mayThrow;
MayThrow* b = noThrow; // implicit conversion to weaker type.
NoThrow* d = noThrow;
NoThrow* c = mayThrow; // no longer compiles!
// Never enforced noexcept, but now won't compile.
// Maybe it can be made to actually work in the future?
std::function<void() noexcept> f;
trigraphs
Removed"Finally??!" != "Finally|"
"Enter date ??/??/??" != R"(Enter date \\??)"
Using-declarations can pull in multiple names
Only practical use seems to be pulling in a method from all base types when using a template pack of bases. This makes it easy to build a type to overload lambdas.
using std::unique_ptr, std::shared_ptr; // meh
struct A { int operator()(int); };
struct B { double operator()(double); };
struct meh : A, B {
using A::operator(), B::operator(); // still meh
};
template <typename... T>
struct overloaded : T... {
using T::operator()...; // now we're cooking!
};
// class template deduction guide:
template <typename... T> overloaded(T...) -> overloaded<T...>;
overloaded<A, B> meh_again; // Overloaded function object
meh_again(1); // returns int
meh_again(1.0); // returns double
auto oh_yeah = overloaded{
[](int) {},
[](double) {},
};
Avoid repeating namespaces in attributes
[[using gnu: cold, noinline]] inline cold_code_path() {}
u8 char literals
I really can't think of a reason to use this unless your source files are in EBCDIC. Only justification I can think of is for symmetry with u8-string literals from C++11.
// New in C++17
auto c1 = u8'c'; // Yup, its a char...
// Before
auto c2 = 'c'; // Also a char
auto c3 = u'c'; // char16_t
auto s1 = u8"s"; // Added in C++11