cpp17 and beyond

54
C++17 and Beyond A Glimpse at the C++ of Tomorrow Andreas Weis Munich C++ User Group, October 2016

Upload: comicsansms

Post on 10-Feb-2017

176 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Cpp17 and Beyond

C++17 and BeyondA Glimpse at the C++ of Tomorrow

Andreas Weis

Munich C++ User Group, October 2016

Page 2: Cpp17 and Beyond

Big Features and Small Features

Bjarne talked a lot about upcoming features in hisCppCon keynote 1

For Bjarne, a big feature is one that changes how we thinkabout programs.

C++11 was a release with big features (Lambdas, MoveSemantics, Variadic Templates, . . . ), C++14 had mostlysmall features.

The hope for C++17 was to again get big features. But itlooks like none of them will make it into the standard in time.

1https://www.youtube.com/watch?v=_wzc7a3McOs

Page 3: Cpp17 and Beyond

Outline

This talk tries to focus on the big features.

We will not discuss C++17 only, but also some of thefeatures that are currently specified as a TS.

Still, there’s lots of stuff that we won’t talk about.

For a complete list of what’s new in C++17, check outAlisdair Meredith’s CppCon talk C++17 in Breadth (NotDepth)2

Feel free to disagree on whether the features discussed herewill have a major impact.

2https://www.youtube.com/watch?v=22jIHfvelZk

Page 4: Cpp17 and Beyond

if constexpr

Basically a smarter #if

Goes back to proposal N3329 from 2012, heavily inspired byD’s static if (introduced there in 2005)3

Was held back because of concerns about overlap withConcepts and uncertainty about scoping

Re-proposed as if constexpr in 2016 (after Concepts Lite),with slightly different semantics

3see also Alexandrescu’s Going Native 2012 talk Static if I had a hammer

Page 5: Cpp17 and Beyond

Compile time branching is weird

template <class T>

struct is_awesome;

template <class T>

void compute_stuff ()

{

if(is_awesome <T>:: value) {

// awesome algorithm

// ...

} else {

// slightly less awesome algorithm

// ...

}

}

Page 6: Cpp17 and Beyond

Compile time branching is weird

Compiler will (insistently) warn that you are branching on acompile-time constant condition.

Both branches need to compile for all valid Ts.

Runtime branching does not cut it. What we really want isconditional compilation.

Page 7: Cpp17 and Beyond

SFINAE to the rescue!

template <class T>

std:: enable_if_t <is_awesome <T>::value >

compute_stuff ()

{

// awesome algorithm

// ...

}

template <class T>

std:: enable_if_t <! is_awesome <T>::value >

compute_stuff ()

{

// slightly less awesome algorithm

// ...

}

Page 8: Cpp17 and Beyond

SFINAE to the rescue?

Lots of boilerplate and code duplication.

Requires intimate knowledge of what many consider a weirdcorner case of C++ templates.

std::enable if makes stuff easier, but is still prone tobreaking in non-obious ways.

Page 9: Cpp17 and Beyond

if constexpr

template <class T>

void compute_stuff ()

{

if constexpr(is_awesome <T>:: value) {

// awesome algorithm

// ...

} else {

// slightly less awesome algorithm

// ...

}

}

The discarded branch is not instantiated by the compiler.Otherwise behaves like a regular if statement.

Page 10: Cpp17 and Beyond

if constexpr

This is less powerful than the initial static if idea, whichwas much closer to #if than to if.

No branching at global or namespace scope.

No layout changes.

if constexpr introduces new scope.

Page 11: Cpp17 and Beyond

Compiler support for if constexpr

Only gcc 7 and clang 3.9 support it already.

Page 12: Cpp17 and Beyond

if and switch with initializer

void safe_init () {

{

std:: lock_guard <std::mutex > lk(mx_);

if(v.empty ()) {

v.push_back(kInitialValue );

}

}

// ...

}

Need an additional pair of {}s for scoping lock

When refactoring, take care that the if cannot be separatedfrom the lock

Page 13: Cpp17 and Beyond

if and switch with initializer

void safe_init () {

if(std::lock_guard <std::mutex > lk(mx_);

v.empty ()) {

v.push_back(kInitialValue );

}

// ...

}

lk is scoped by the if.

Page 14: Cpp17 and Beyond

if and switch with initializer

if(auto p = m.try_emplace(key , value);

!p.second)

{

FATAL("Element already registered");

} else {

process(p.second );

}

Avoids polluting the enclosing namespace with identifiers.

Page 15: Cpp17 and Beyond

Compiler support for if and switch with initializer

Only gcc 7 and clang 3.9 support it already.

Page 16: Cpp17 and Beyond

Structured Bindings

std::tie is not as useful as it should be:

std::map <std::string , int > my_map;

auto const ret = my_map.insert ({"key", 42});

std::map <std::string , int >:: iterator it;

bool was_inserted;

std::tie(it , was_inserted) = ret;

We lose type deduction for the iterator type.

We lose the const qualifier.

Separate declaration often not feasible for non-trivial types.

Page 17: Cpp17 and Beyond

Structured Bindings - To the rescue!

std::map <std::string , int > my_map;

auto const [it , was_inserted] =

my_map.insert ({"key", 42});

Page 18: Cpp17 and Beyond

Structured Bindings - Destructuring

But wait, there’s more:

struct Foo {

int i;

float f;

std:: string s;

};

Foo f;

// ...

auto [natural , real , text] = f;

Page 19: Cpp17 and Beyond

Structured Bindings

Alright, cool. But how is this a major feature?

Page 20: Cpp17 and Beyond

Some preliminary boilerplate...

struct any_type {

template <class T>

constexpr operator T(); // non explicit

};

template <class T, class ... TArgs >

class is_braces_constructible;

// std:: true_type if we can construct

// a T from the TArgs

Page 21: Cpp17 and Beyond

Struct to tuple

template <class T> auto to_tuple(T&& object) {

using type = std::decay_t <T>;

if constexpr(is_braces_constructible

<type , any_type , any_type >{})

{

auto&& [p1, p2] = object;

return std:: make_tuple(p1 , p2);

} else if constexpr(is_braces_constructible

<type , any_type >{})

{

auto&& [p1] = object;

return std:: make_tuple(p1);

} else {

return std:: make_tuple ();

}

}

Page 22: Cpp17 and Beyond

Simple compile-time reflection

Full example athttps://www.reddit.com/r/cpp/comments/4yp7fv/c17_

structured_bindings_convert_struct_to_a_tuple/.

Once we have tuples, we can do all kinds ofmetaprogramming (see Boost Hana)

Not clear how much benefit structured bindings really bring,but chances are they will turn out more powerful than theyseem at first glance.

Page 23: Cpp17 and Beyond

Structured bindings + if with initializer

std::map <std::string , int > my_map;

if(auto const [it , was_inserted] =

my_map.insert ({"key", 42});

!was_inserted)

{

throw InsertionFailed ();

}

Page 24: Cpp17 and Beyond

Compiler support for structured bindings

Only (partially) supported in Clang 4.0 today.

Page 25: Cpp17 and Beyond

Concepts TS (not part of C++17)

Specify constraints for template arguments.

template <typename T>

T const& min(T const& a, T const& b)

{

return (a < b) ? a : b;

}

Page 26: Cpp17 and Beyond

Concepts Example

template <typename T>

concept bool LTComparable = requires(T a, T b)

{

{ a < b } -> bool;

};

template <LTComparable T>

T const& min(T const& a, T const& b)

{

return (a < b) ? a : b;

}

Page 27: Cpp17 and Beyond

Error messages with concepts

Here’s what happens if we try to use min with a type Foo thatdoes not have operator<:

In function ’int main()’:

error: cannot call function ’const T& min(const T&, const T&) [with T = Foo]’

std::cout << min(a, b) << std::endl;

^

note: constraints not satisfied

T const& min(T const& a, T const& b)

^~~

note: within ’template <class T> concept const bool LTComparable <T> [with T = Foo]’

concept bool LTComparable = requires(T a, T b) {

^~~~~~~~~~~~

note: with ’Foo a’

note: with ’Foo b’

note: the required expression ’(a < b)’ would be ill -formed

Page 28: Cpp17 and Beyond

Error messages with concepts

Here’s what happens if there is an operator<, but it does notreturn bool:In function ’int main()’:

error: cannot call function ’const T& min(const T&, const T&) [with T = Foo]’

std::cout << min(a, b) << std::endl;

^

note: constraints not satisfied

T const& min(T const& a, T const& b)

^~~

note: within ’template <class T> concept const bool LTComparable <T> [with T = Foo]’

concept bool LTComparable = requires(T a, T b) {

^~~~~~~~~~~~

note: with ’Foo a’

note: with ’Foo b’

note: ’operator <(a, b)’ is not implicitly convertible to ’bool ’

Page 29: Cpp17 and Beyond

Concepts

Concepts are not checked for completeness. Theimplementation may use a concept parameter like anytypename.

template <LTComparable T>

T const& max(T const& a, T const& b)

{

return (a > b) ? a : b;

}

Constraints can be used instead of SFINAE to removefunctions from the overload set.

template <typename T>

requires std:: is_integral <T>:: value

void ints_only(T i);

Page 30: Cpp17 and Beyond

Writing concepts

Defining good concepts is non-trivial. Concepts aremeant to represent fundamental concepts in anapplication domain (hence the name ”concepts”).Similarly throwing together a set of syntactic constraintsto be used for a the arguments for a single class oralgorithm is not what concepts were designed for and willnot give the full benefits of the mechanism.

From the C++ Core Guidelines 4

4https://github.com/isocpp/CppCoreGuidelines/blob/master/

CppCoreGuidelines.md#

t20-avoid-concepts-without-meaningful-semantics

Page 31: Cpp17 and Beyond

Writing concepts

In the current TS, concepts only allow checking syntacticalproperties.

However, concepts should only be designed from meaningfulsemantics (potentially including space/time complexity).

Unfortunately, we cannot have non-trivial semantics withoutproofs.

Concepts0x approximated proofs with axioms, in ConceptsLite they are gone completely.

Page 32: Cpp17 and Beyond

Writing concepts

Do not be mislead by the current feature state concepts!Concepts are not about syntax.

Always design your concepts on paper from clear, logicalsemantics.

Then try to identify the parts that overlap with what thecompiler can check.

Take the compiler’s help where you can get it. Document therest.

Important is not how much support you get from thecompiler, but how you think about your code.

Page 33: Cpp17 and Beyond

Thinking about concepts

Alex Stepanov’s books 5

Take a look at functional programming

5If you have only time for one, choose the one on the right

Page 34: Cpp17 and Beyond

Compiler support for concepts

Currently only on gcc 6.1 with -fconcepts

Stepanov-style concepts

#if !defined __cpp_concepts || \

__cpp_concepts < 201507

# define LTComparable typename

#endif

template <LTComparable T>

T const& min(T const& a, T const& b);

Page 35: Cpp17 and Beyond

Library Time!

Page 36: Cpp17 and Beyond

Vocabulary Types - std::optional

std::optional <Value > readValue ();

// ...

auto const v = readValue ();

if(v) { useValue (*v); }

useValue(v.value_or(default_value ));

Page 37: Cpp17 and Beyond

Constructing std::optional

auto empty = std::optional <Value >();

auto also_empty = std:: optional(std:: nullopt );

auto def_constr = std:: make_optional <Value >();

auto value = std:: make_optional(v);

auto from_args =

std:: make_optional <Value >(arg1 , arg2);

auto from_init_list =

std:: make_optional <std::vector <int >>

({1,2,3,4,5});

Page 38: Cpp17 and Beyond

Compiler support for std::optional

Supported in clang 4, gcc 7 and VC 15.

Also: Boost.Optional in Boost 1.30 with slightly differentinterface.

Page 39: Cpp17 and Beyond

Vocabulary Types - std::variant

std::variant <int , float , std::string > v = 42;

int n = std::get <int >(v);

v = std:: string("Narf");

std:: string s = std::get <std::string >(v);

float f = std::get <float >(v);

// throws std:: bad_variant_access

Page 40: Cpp17 and Beyond

Polymorphism with variants

struct Circle { float getArea () const; };

struct Square { float getArea () const; };

struct Triangle { float getArea () const; };

typedef variant <Circle , Square , Triangle > Shape;

struct GetShapeAreaVisitor {

template <typename T>

float operator ()(T const& s) {

return s.getArea ();

}

};

float getArea(Shape const& s) {

return std::visit(GetShapeAreaVisitor {}, s);

}

Page 41: Cpp17 and Beyond

Variant details

std::variant is almost-never-empty.

A variant always holds an instance of one of its Ts.

If a type-changing assignment throws, variant might have torevert to a special valueless statevalueless by exception().

In the absence of exceptions, variants give the never-emptyguarantee.

Supported in gcc 7 and VC 15.

Available with slightly different interface and semantics inBoost 1.31.

Page 42: Cpp17 and Beyond

Vocabulary Types - std::any

std::any any_value;

assert (! any_value.has_value ());

any_value = std::make_any <std::string >("narf");

any_value = std::make_any <int >(42);

int i = std::any_cast <int >( any_value );

auto* is_null =

std::any_cast <std::string >(& any_value );

std::any_cast <std::string >( any_value );

// throws std:: bad_any_cast

Page 43: Cpp17 and Beyond

Details of std::any

Prefer std::variant, if possible.

Type-erasure in std::any requires heap allocation.

Little better than a void*.

Supported in gcc 7, clang 4 and VC 15.

Available in Boost 1.23 with slightly different interface.

Page 44: Cpp17 and Beyond

Vocabulary Types - std::string view

void processString(char const* str);

// loses length

void processString(std:: string const& str);

// might make a copy

void processString(char const* str ,

std:: size_t len);

// yuck!

Page 45: Cpp17 and Beyond

Vocabulary Types - std::string view

void processString(std:: string_view const& str);

Page 46: Cpp17 and Beyond

Details of std::string view

A non-owning view on an immutable string.

Implemented as a std::basic string view template,similar to std::string.

Can be constructed from a std::string or a const pointer.

When constructing from pointer, if no length is given, stringmust be null-terminated and length will be computed atconstruction.

Constructing views on a substring requires no copy.

Supported in gcc 7, clang 4 and VC 15.

Available in the GSL(https://github.com/Microsoft/GSL)

Page 47: Cpp17 and Beyond

Ranges TS (not part of C++17)

The STL achieves great things by separating algorithms fromdata structures through iterators.

But iterators can be awkward to use.

c.erase(remove_if(begin(c), end(c), pred),

end(c));

Ranges consists of two parts:

An STL-like library that is based on ranges instead of iterators.Works with the existing STL containers.Concepts for both the classic STL and the new ranges parts

Page 48: Cpp17 and Beyond

Sentinels

Iterators point to elements.

A range in STL is spanned by an iterator to the first elementand an iterator to one-past-the-last.

Except that end iterator might not really point to anything. Ifwe are dealing with a range where not all elements are storedin memory, obtaining and end iterator might actually requireiterating the whole range.

Ranges introduces the notion of a sentinel.

A sentinel can be compared to an iterator. Both are equal ifthe iterator points to the end element.

A range in ranges is spanned by an iterator and a sentinel.

Page 49: Cpp17 and Beyond

What is a range?

Something that you can call begin and end on.

template <class T>

using iterator_t =

decltype(ranges ::begin(declval <T& >()));

template <class T>

using sentinel_t =

decltype(ranges ::end(declval <T& >()));

template <class T>

concept bool Range() {

return requires {

typename sentinel_t <T>;

};

};

Page 50: Cpp17 and Beyond

Ranges - Actions

In-place mutation.

Composable.

action :: remove_if(c, pred);

Pipe syntax:

vi = std::move(vi) |

action ::sort |

action :: unique;

Page 51: Cpp17 and Beyond

Ranges - Views

Non-mutating, lazy sequence manipulation.

Composable.

Cheap to create and copy.

auto odd = []( int i){ return i % 2 == 1; };

auto to_str = []( int i){

return std:: to_string(i);}

using namespace ranges;

std::vector <int > vi = view::ints (1 ,11);

// vi = {1,2,3,4,5,6,7,8,9,10};

auto rng = vi | view:: remove_if(odd)

| view:: transform(to_str );

// rng == {"2" ,"4" ,"6" ,"8" ,"10"};

Page 52: Cpp17 and Beyond

Ranges - Compiler support

You can use ranges today!

Reference implementation range-v36 works on gcc 4.8.5,clang 3.5.2; Microsoft offers a fork that works on VC 147

Full experience requires concepts language support by thecompiler, but even without that it’s a pretty great library.

Also: If you find bugs in the TS spec now, it is way easier tofix than once it goes IS.

6https://github.com/ericniebler/range-v37github.com/microsoft/Range-V3-VS2015

Page 53: Cpp17 and Beyond

Wrapping up...

Lots of features are available for use already today, especiallyfrom the library.

Small features can make a big difference in everyday work.

Other features might be bigger than they seem at first.

Don’t let lack of compiler support keep you from changinghow you think about your code today.

Page 54: Cpp17 and Beyond

Thanks for your attention.