1 of 40

C++17 and Beyond

Mark Isaacson

1

2 of 40

Roadmap

  • What’s coming in C++17 and C++20
  • The surprising utility of std::string_view
  • C++17’s most deliciously scary feature: operator dot
  • Making templates more accessible with constexpr if
  • Design by Introspection

If there's time:

  • Generic Transaction class with std::uncaught_exceptions

2

3 of 40

What made it to C++17

  • Filesystem TS
  • Special Math TS
  • Parallelism TS
  • Library Fundamentals 1 TS
  • constexpr if
  • [[fallthrough]]
  • [[nodiscard]]
  • logical operator type traits
  • variable templates for type_traits
  • polymorphic allocators
  • operator dot?
  • std::not_fn
  • std::string_view
  • std::any (unconstrained)
  • std::optional
  • std::shared_ptr for arrays
  • std::uncaught_exceptions
  • std::invoke
  • std::apply

3

4 of 40

C++20 Horizon

  • Concurrency TS
  • Transactional Memory TS
  • Concepts TS
  • Ranges TS
  • Networking TS
  • Library Fundamentals 2 TS
  • Parallelism 2 TS

Longer?

  • Modules TS
  • Concurrency 2 TS
  • 2D Graphics TS

4

5 of 40

Fun.

5

6 of 40

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

7 of 40

Good News

  • This is automatically detectable
  • Best practices avoid this entirely (for string constants)

ASAN_OPTIONS=check_initialization_order=1:strict_init_order=1:

detect_odr_violation=1

7

8 of 40

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

9 of 40

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

10 of 40

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

11 of 40

Why string_view

string_view observer_ptr<T> (or T*) as string unique_ptr<T>

  • Performance
  • Self-documentation
  • Interface safety/convenience

11

12 of 40

Operator Dot

struct Int {

int& operator.() {

return value;

}

int value = 0;

};

Int x;

x = 5;

x += 5;

cout << x << endl;

12

13 of 40

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);

14 of 40

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

15 of 40

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

16 of 40

Contracts w/ Operator Dot

struct BoundedInt {

int& operator.() {

return val;

}

int val = 0;

};

16

17 of 40

Contracts w/ Operator Dot

struct BoundedInt {

~BoundedInt() {

if (val < 0 || val > 100)

LOG(ERROR) << "BoundsErr";

}

int& operator.() {return val;}

int val = 0;

};

17

18 of 40

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

19 of 40

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

20 of 40

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

21 of 40

Operator Dot

Potential uses I won't outline:

  • Smart references
  • Pimpl idiom
  • Proxies/Mixins (add operators) - see last year's talk :)

21

22 of 40

Operator Dot vs Inheritance

  • Can extend primitive types (like int)
  • Hides type aliases
  • No notion of final or protected
  • Wrapped type can't call Wrapper's functions (no NVI pattern)

22

23 of 40

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

24 of 40

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

25 of 40

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

26 of 40

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

27 of 40

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

28 of 40

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

29 of 40

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

30 of 40

Fold Expressions

template<typename Bool, typename... Rest>

constexpr bool conjunction(Bool&& b, Rest&&... rest) {

return b && ...;

}

constexpr bool enabled = conjunction(true, false, true);

30

31 of 40

constexpr if vs Concepts

  • constexpr if => Template branching
  • Concepts => Template constraints

31

32 of 40

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

33 of 40

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

34 of 40

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

35 of 40

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

36 of 40

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

37 of 40

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

38 of 40

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

39 of 40

Review

  • string_view provides correctness, efficiency, and interoperability
  • Operator dot is black magic
  • constexpr if makes branching templates easy

39

40 of 40

return 0;

Mark Isaacson

http://modernmaintainablecode.com

40