Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

C++17 in Detail: Code Simplification

DZone's Guide to

C++17 in Detail: Code Simplification

With the C++17 specification, new features are being introduced to help make your code cleaner, nicer, and generally simpler. Read on for the details.

Free Resource

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.

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 others.

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.

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 the initializer is an array:

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


2. If the initializer supports std::tuple_size<> and provides a 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 the initializer’s type contains only nonstatic, public members:

struct S { 
  int x1 : 2; 
  volatile double y1; 
}; 

S f(); 

const auto [ 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 uses (support to for loops!):

std::map myMap; 

for (const auto & [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 the 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"; 

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

const auto 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:

{ 
  const auto it = myString.find("Hello"); 

  if (it != std::string::npos) 
    std::cout << it << " Hello\n" 
} 

{ 
  const auto 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 (const auto it = myString.find("Hello"); it != std::string::npos) 
  std::cout << it << " Hello\n"; 

if (const auto 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 (const auto 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 the constexpr keyword allow us 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.

struct MyClass { 
  static const int sValue; 
}; 

inline int const MyClass::sValue = 777;


Or even:

struct MyClass { 
  inline static const int 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 this 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> 
constexpr int fibonacci() {
  return fibonacci<N-1>() + fibonacci<N-2>(); 
} 

template<>
constexpr int fibonacci<1>() { 
  return 1; 
} 

template<> 
constexpr int fibonacci<0>() { 
  return 0; 
}


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

template<int N> 
constexpr int fibonacci() { 
  if constexpr (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:

if constexpr (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 C++Now 2017: Bryce Lelbach “C++17 Features”/16th minute, there’s a nice example where constexpr if can be used to define theget<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) { 
  if constexpr (I == 0) 
    return s.n; 
  else if constexpr (I == 1) 
    return s.s; 
  else if constexpr (I == 2) 
    return s.d; 
}


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 without doing much 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.

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 us 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!

Topics:
c++ ,performance ,clean code

Published at DZone with permission of Bartłomiej Filipek, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}