C++17 and Beyond
Mark Isaacson
1
Roadmap
If there's time:
2
What made it to C++17
3
C++20 Horizon
Longer?
4
Fun.
5
Subtle Bug
// Foo.h
#include <string>
extern string kFoo;
// Foo.cpp
#include "Foo.h"
const string kFoo = "foo";
// main.cpp
#include "Foo.h"
const string kBar = kFoo;
int main() { return 0; }
6
Good News
ASAN_OPTIONS=check_initialization_order=1:strict_init_order=1:
detect_odr_violation=1
7
Pre-C++17
// Foo.h
extern const char* const kFoo;
// Foo.cpp
#include "Foo.h"
const char* const kFoo = "foo";
// main.cpp
#include "Foo.h"
const char* const kBar = kFoo;
int main() { return 0; }
8
Post-C++17
// Foo.h
#include <string_view>
const constexpr string_view kFoo = "foo";
// main.cpp
#include "Foo.h"
const constexpr string_view kBar = kFoo;
int main() { return 0; }
9
Post-C++17
const char* const kFoo = "foo";
const char* const kBar = "bar";
const constexpr string_view kFoo2 = "foo";
const constexpr string_view kBar2 = "bar";
assert(kFoo == "foo"); // Oops
assert(kFoo == kBar); // Oops
assert(kFoo2 == "foo"); // Ok
assert(kFoo2 == kBar2); // Ok
assert(kFoo2 == kFoo); // Ok
10
Why string_view
string_view → observer_ptr<T> (or T*) as string → unique_ptr<T>
11
Operator Dot
struct Int {
int& operator.() {
return value;
}
int value = 0;
};
Int x;
x = 5;
x += 5;
cout << x << endl;
12
Operator Dot
struct Int {
int& operator.() {
return value;
}
int value = 0;
};
Int x;
x = 5;
x += 5;
cout << x << endl;
13
"Lowers" to x.operator+=(5);
Operator Dot
struct Int {
int& operator.() {
return value;
}
string toString() {
return to_string(value);
}
int value = 0;
};
Int x;
x = 5;
x += 5;
cout << x << endl;
string msg =
"The magic number is " +
x.toString();
14
Contracts w/ Operator Dot
Goal: an int with the invariant: 0 <= x <= 100
BoundedInt x = 5;
x += 5; //Ok
x = 1000; //Error, exceeded bound of 100
15
Contracts w/ Operator Dot
struct BoundedInt {
int& operator.() {
return val;
}
int val = 0;
};
16
Contracts w/ Operator Dot
struct BoundedInt {
~BoundedInt() {
if (val < 0 || val > 100)
LOG(ERROR) << "BoundsErr";
}
int& operator.() {return val;}
int val = 0;
};
17
Contracts w/ Operator Dot
struct BoundedInt {
Impl operator.()
{return Impl{val};}
int val = 0;
};
// Example use:
BoundedInt x = 5;
x += 5; //Ok
x = 1000; //Error
struct Impl {
Impl(int& x): ref{x} {};
~Impl() {
if (ref < 0 || ref > 100)
LOG(ERROR) << "BoundsErr";
}
int& operator.() {return ref;}
int& ref;
};
18
Contracts w/ Operator Dot
struct BoundedInt {
Impl operator.()
{return Impl{val};}
int val = 0;
};
// Example use:
BoundedInt x = 5;
x += 5; //Ok
x = 1000; //Error
struct Impl {
Impl(int& x): ref{x} {};
~Impl() {
if (ref < 0 || ref > 100)
LOG(ERROR) << "BoundsErr";
}
int& operator.() {return ref;}
int& ref;
};
19
2
1
3
4
Contracts w/ Operator Dot
struct BoundedInt {
Impl operator.()
{return Impl{val};}
int val = 0;
};
// Example use:
BoundedInt x = 5;
x += 5; //Ok
x = 1000; //Error
struct Impl {
Impl(int& x): ref{x},copy{x} {};
~Impl() {
if (copy < 0 || copy > 100)
LOG(ERROR) << "BoundsErr";
ref = copy;
}
int& operator.() {return copy;}
int& ref; int copy;
};
20
Operator Dot
Potential uses I won't outline:
21
Operator Dot vs Inheritance
22
Compile-time branching
template<typename T>
struct default_delete {
void operator()(T* ptr) const { delete ptr; }
};
template<typename T>
struct default_delete<T[]> {
template<typename U>
void operator()(U* ptr) const { delete[] ptr; }
};
23
Compile-time branching
template<typename T>
struct default_delete {
void operator()(T* ptr) const { delete ptr; }
};
template<typename T>
struct default_delete<T[]> {
template<typename U>
void operator()(U* ptr) const { delete[] ptr; }
};
24
Compile-time branching
template<typename T>
struct default_delete {
template<typename U> void operator()(U* ptr) const {
if (is_array<T>::value) {
delete[] ptr;
} else {
delete ptr;
}
}
};
25
Compile-time branching
constexpr bool example1 = conjunction(true); // true
constexpr bool example2 = conjunction(false); // false
constexpr bool example3 = conjunction(true, true); // true
constexpr bool example4 = conjunction(true, false, true); // false
26
Compile-time branching
template<typename Bool>
constexpr bool conjunction(Bool&& b) {
return b;
}
template<typename Bool, typename... Rest>
constexpr bool conjunction(Bool&& b, Rest&&... rest) {
return b && conjunction(std::forward<Rest>(rest)...);
}
constexpr bool enabled = conjunction(true, false, true);
27
Compile-time branching
template<typename Bool>
constexpr bool conjunction(Bool&& b) {
return b;
}
template<typename Bool, typename... Rest>
constexpr bool conjunction(Bool&& b, Rest&&... rest) {
return b && conjunction(std::forward<Rest>(rest)...);
}
constexpr bool enabled = conjunction(true, false, true);
28
Compile-time branching
template<typename Bool, typename... Rest>
constexpr bool conjunction(Bool&& b, Rest&&... rest) {
constexpr if (sizeof...(Rest)) {
return b && conjunction(std::forward<Rest>(rest)...);
} constexpr else {
return b;
}
}
constexpr bool enabled = conjunction(true, false, true);
29
Fold Expressions
template<typename Bool, typename... Rest>
constexpr bool conjunction(Bool&& b, Rest&&... rest) {
return b && ...;
}
constexpr bool enabled = conjunction(true, false, true);
30
constexpr if vs Concepts
31
constexpr if in C++14
Widget w{};
foobar(w);
template<typename T>
void foobar(T&& x) {
CONSTEXPR_IF(has_foo<T>::value, { x.foo(); });
CONSTEXPR_IF(has_bar<T>::value, { x.bar(); });
}
struct Widget {
void foo() {}
};
32
constexpr if in C++14
CONSTEXPR_IF(true, { cout << "hi"; });
template<bool b, typename F>
struct CallIfImpl {
CallIfImpl(F func) {}
};
template<typename F>�struct CallIfImpl<true, F> {� CallIfImpl(F func) {� auto dummyArgument = true;� func(dummyArgument);� }�};
33
constexpr if in C++14
CONSTEXPR_IF(true, { cout << "hi"; });
template<bool b, typename F>�void callIf(F&& func) {
CallIfImpl<b, F>{forward<F>(func)};�}
template<bool b, typename F>
struct CallIfImpl {
CallIfImpl(F func) {}
};
template<typename F>�struct CallIfImpl<true, F> {� CallIfImpl(F func) {� auto dummyArgument = true;� func(dummyArgument);� }�};
34
constexpr if in C++14
CONSTEXPR_IF(true, { cout << "hi"; });
template<bool b, typename F>�void callIf(F&& func) {
CallIfImpl<b, F>{forward<F>(func)};�}
#define CONSTEXPR_IF(condition, code) \
callIf<condition>([&](auto) code);
template<bool b, typename F>
struct CallIfImpl {
CallIfImpl(F func) {}
};
template<typename F>�struct CallIfImpl<true, F> {� CallIfImpl(F func) {� auto dummyArgument = true;� func(dummyArgument);� }�};
35
Design by Introspection
Widget w{};
foobar(w);
template<typename T>
void foobar(T&& x) {
CONSTEXPR_IF(has_foo<T>::value, { x.foo(); });
CONSTEXPR_IF(has_bar<T>::value, { x.bar(); });
}
struct Widget {
void foo() {}
};
36
Has Member
template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t =
typename make_void<Ts...>::type;
template<typename T, typename=void_t<>>
struct has_foo_impl : std::false_type {};
template<typename T> struct has_foo_impl
<T, void_t<decltype(std::declval<T&>().foo())>> : std::true_type {};
template<typename T> struct has_foo
: has_foo_impl<T, void_t<>> {};
37
What is Design by Introspection?
template<typename ITR>
void advance(ITR& itr, size_t n) {
using C = typename iterator_traits<ITR>::iterator_category;
constexpr if (is_same_v<C, random_access_iterator_tag>) {
itr += n;
} constexpr else {
for (auto i = 0u; i < n; ++i) ++itr;
}
}
38
Review
39
return 0;
40