Sponsors: KDAB and Whole Tomato Software

24 July 2017

C++17 in details: Code Simplification

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 make your code more compact right off the bat.

  • 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 - Filesystem
  7. Library changes - Parallel Algorithms
  8. Library changes - Utils (soon)
  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 should probably start looking into it. Not only are tuples suggested for returning multiple values from a function, but they've also got special language support - so that the code is even easier and 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; 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 usage (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 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 for 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

Hasn’t C++ Become More Pythonic? - as was written in one blog post from Jeff Preshing? :)

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 you to declare and define static variables in one place, but it’s limited to constexpr’essions only. I’ve even asked the 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, but 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 was 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 a bit 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 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)
{
    if constexpr (I == 0)
        return s.n;
    else if constexpr (I == 1)
        return s.s;
    else if constexpr (I == 2)
        return s.d;
}

Versus previously you would have needed 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 which is 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 another. In this post, I focused on the bigger parts, also without doing much repetition.

Still, just for recall you might want to consider the following features, which 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 you 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! (STL: Filesystem)

remember about my C++17 Ref Card:

Interested in new blog posts and bonus content? Sign up for my newsletter.

© 2017, Bartlomiej Filipek, Blogger platform
Any opinions expressed herein are in no way representative of those of my employers.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.