Quantcast
Channel: Bartek's coding blog
Viewing all articles
Browse latest Browse all 325

C++17 in details: Code Simplification

$
0
0

C++17 features, simplifications

With each C++ standard, we aim for simpler, cleaner and more expressive code. C++17 offers several "big" language features that should make our code nicer. Let’s have a look.

Intro

You might say that most of the new language features (not to mention The Standard Library improvements) are there to write simpler/cleaner code. The “C++17 in details” series reviews most of the bigger things, still for today, I tried to pick a few features that out of the box make your code more compact.

  • Structured bindings/Decomposition declarations
  • Init-statement for if/switch
  • Inline variables
  • constexpr if (again!)
  • a few other mentions

The Series

This post is a fifth in the series about C++17 features details.

The plan for the series

  1. Fixes and deprecation
  2. Language clarification
  3. Templates
  4. Attributes
  5. Simplification (today)
  6. Library changes 1 (soon)
  7. Library changes 2 (soon + 2)
  8. Library changes 3 (soon + 3)
  9. Wrap up, Bonus

Just to recall:

First of all, if you want to dig into the standard on your own, you can read the latest draft here:

N4659, 2017-03-21, Working Draft, Standard for Programming Language C++ - the link also appears on the isocpp.org.

And you can also grab my list of concise descriptions of all of the C++17 language features:

It’s a one-page reference card, PDF.

Links:

OK, let’s discuss the features!

Structured Binding Declarations

Do you often work with tuples?

If not, then you probably should start looking at it. Not only tuples are suggested for returning multiple values from a function, but also they got special language support - so that the code is even easier/cleaner.

For example (got it from std::tie at cppreference):

std::set<S> mySet;

S value
{42,"Test",3.14};
std
::set<S>::iterator iter;
bool inserted;

// unpacks the return val of insert into iter and inserted
std
::tie(iter, inserted)= mySet.insert(value);

if(inserted)
std
::cout <<"Value was inserted\n";

Notice that you need to declare iter and inserted first. Then you can use std::tie to make the magic… Still, it’s a bit of code.

With C++17:

std::set<S> mySet;

S value
{42,"Test",3.14};

auto[iter, inserted]= mySet.insert(value);

One line instead of three! It’s also easier to read and safer, isn’t it?

Also, you can now use const and write const auto [iter, inserted] and be const correct.

Structured Binding is not only limited to tuples, we have three cases:

1. If initializer is an array:

// works with arrays:
double myArray[3]={1.0,2.0,3.0};
auto[a, b, c]= myArray;

2. if initializer supports std::tuple_size<> and provides get<N>() function (the most common case I think):

auto[a, b]= myPair;// binds myPair.first/second

In other words, you can provide support for your classes, assuming you add get<N> interface implementation.

3. if initializer’s type contains only non static, public members:

struct S {int x1 :2;volatiledouble y1;};
S f
();
constauto[ x, y ]= f();

Now it’s also quite easy to get a reference to a tuple member:

auto&[ refA, refB, refC, refD ]= myTuple;

And one of the coolest usage (support to for loops!):

std::map myMap;
for(constauto&[k,v]: myMap)
{
// k - key
// v - value
}

BTW: Structured Bindings or Decomposition Declaration?

For this feature, you might have seen another name “decomposition declaration” in use. As I see this, those two names were considered, but now the standard (the draft) sticks with “Structured Bindings.”

More Details in:

Working in GCC: 7.0, Clang: 4.0, MSVC: VS 2017.3

Init-statement for if/switch

New versions of the if and switch statements for C++:

if (init; condition) and switch (init; condition).

Previously you had to write:

{
auto val =GetValue();
if(condition(val))
// on success
else
// on false...
}

Look, that val has a separate scope, without that it ‘leaks’ to enclosing scope.

Now you can write:

if(auto val =GetValue(); condition(val))
// on success
else
// on false...

val is visible only inside the if and else statements, so it doesn’t ‘leak.’
condition might be any condition, not only if val is true/false.

Why is this useful?

Let’s say you want to search a few things in a string:

const std::string myString ="My Hello World Wow";

constauto it = myString.find("Hello");
if(it != std::string::npos)
std
::cout << it <<" Hello\n"

constauto it2 = myString.find("World");
if(it2 != std::string::npos)
std
::cout << it2 <<" World\n"

We have to use different names for it or enclose it with a separate scope:

{
constauto it = myString.find("Hello");
if(it != std::string::npos)
std
::cout << it <<" Hello\n"
}

{
constauto it = myString.find("World");
if(it != std::string::npos)
std
::cout << it <<" World\n"
}

The new if statement will make that additional scope in one line:

if(constauto it = myString.find("Hello"); it != std::string::npos)
std
::cout << it <<" Hello\n";

if(constauto it = myString.find("World"); it != std::string::npos)
std
::cout << it <<" World\n";

As mentioned before, the variable defined in the if statement is also visible in the else block. So you can write:

if(constauto it = myString.find("World"); it != std::string::npos)
std
::cout << it <<" World\n";
else
std
::cout << it <<" not found!!\n";

Plus, you can use it with structured bindings (following Herb Sutter code):

// better together: structured bindings + if initializer
if(auto[iter, succeeded]= mymap.insert(value); succeeded){
use(iter);// ok
// ...
}// iter and succeeded are destroyed here

More details in

GCC: 7.0, Clang: 3.9, MSVC: VS 2017.3.

Inline variables

With Non-Static Data Member Initialization (see my post about it here), we can now declare and initialize member variables in one place. Still, with static variables (or const static) you usually need to define it in some cpp file.

C++11 and constexpr keyword allow to declare and define static variables in one place, but it’s limited to constexpr’essions only. I’ve even asked a question: c++ - What’s the difference between static constexpr and static inline variables in C++17? - Stack Overflow - to make it a bit clear.

Ok, but what’s the deal with this feature:

Previously only methods/functions could be specified as inline, now you can do the same with variables, inside a header file.

A variable declared inline has the same semantics as a function declared inline: it can be defined, identically, in multiple translation units, must be defined in every translation unit in which it is used, and the behavior of the program is as if there is exactly one variable.

structMyClass
{
staticconstint sValue;
};

inlineintconstMyClass::sValue =777;

Or even:

structMyClass
{
inlinestaticconstint sValue =777;
};

Also, note that constexpr variables are inline implicitly, so there’s no need to use constexpr inline myVar = 10;.

Why can it simplify the code?

For example, a lot of header only libraries can limit the number of hacks (like using inline functions or templates) and just use inline variables.

The advantage over constexpr is that your initialization expression doesn’t have to be constexpr.

More info in:

GCC: 7.0, Clang: 3.9, MSVC: not yet

constexpr if

I’ve already introduced that feature in my previous post about templates: templates/constexpr-if. It was only a brief description, so now we can think about examples that shed some more light on the feature.

Regarding code samples? Hmm… As you might recall constexpr if can be used to replace several tricks that were already done:

  • SFINAE technique to remove not matching function overrides from the overload set
    • you might want to look at places with C++14’s std::enable_if - that should be easily replaced by constexpr if.
  • Tag dispatch

So, in most of the cases, we can now just write a constexpr if statement and that will yield much cleaner code. This is especially important for metaprogramming/template code that is, I think, complex by its nature.

A simple example: Fibonacci:

template<int  N>
constexprint fibonacci(){return fibonacci<N-1>()+ fibonacci<N-2>();}
template<>
constexprint fibonacci<1>(){return1;}
template<>
constexprint fibonacci<0>(){return0;}

Now, it can be written almost in a ‘normal’ (no compile time version):

template<int N>
constexprint fibonacci()
{
ifconstexpr(N>=2)
return fibonacci<N-1>()+ fibonacci<N-2>();
else
return N;
}

In C++ Weekly episode 18 Jason Turner makes an example that shows that constexpr if won’t do any short circuit logic, so the whole expression must compile:

ifconstexpr(std::is_integral<T>::value &&
std
::numeric_limits<T>::min()<10)
{

}

For T that is std::string you’ll get a compile error because numeric_limits are not defined for strings.

In the C++Now 2017: Bryce Lelbach “C++17 Features”/16th minute there’s a nice example, where constexpr if can be used to define get<N> function - that could work for structured bindings.

struct S 
{
int n;
std
::string s;
float d;
};

template<std::size_t I>
auto&get(S& s)
{
ifconstexpr(I ==0)
return s.n;
elseifconstexpr(I ==1)
return s.s;
elseifconstexpr(I ==2)
return s.d;
}

VS previously you would have to write:

template<>auto&get<0>(S &s){return s.n;}
template<>auto&get<1>(S &s){return s.s;}
template<>auto&get<2>(S &s){return s.d;}

As you can see it’s questionable what’s the simpler code here. Although in this case, we’ve used only a simple struct, with some real world examples the final code would be much more complex and thus constexpr if would be cleaner.

More details:

MSVC 2017.3, GCC: 7.0, Clang: 3.9.

Other features

We can argue that most of the new features of C++ simplify the language in one way or the other. In this post, I focused on the bigger parts, also without doing much of repetition.

Still, just for recall you might want to consider the following features, they also make the code simpler:

Not to mention a lot of library features! But we’ll cover them later :)

Summary

In my opinion, C++17 makes real progress towards compact, expressive and easy to read code.

One of the best things is constexpr if that allows to write template/metaprogramming code in a similar way to standard code. For me, it’s a huge benefit (as I am always frightened of those scary template tricks).

The second feature: structured bindings (that works even in for loops) feels like code from dynamic languages (like Python).

As you can see all of the mentioned features are already implemented in GCC and Clang. If you work with the recent versions of those compilers you can immediately experiment with C++17. Soon, a lot of those features will be available in VS: VS 2017.3

  • What are your best C++17 language features that make code cleaner?
  • Have you played with constexpr if or structured bindings?

For now, we’ve covered most of the language features, so now it’s time to move to some new things in the Standard Library. Stay tuned for the next articles in the series!

remember about my C++17 Ref Card:


Viewing all articles
Browse latest Browse all 325

Trending Articles