This is part of my weekly C++ posts based on the daily C++ tips I make at my work. I strongly recommend this practice. If you dont have it in your company start it.
List of all weekly posts can be found here.
1. Trailing return type
In C++11 we got trailing return type. That is we can write the return type at the end:
reason for this was lambdas with multiple return statements and function templates that that have return type dependent on the input parameter types:
however from C++14 we have Generalized return type deduction and now in most of the cases you can omit writing the trailing return type and just use auto.
Where you can not omit it is when you have return statements that return different types. The compiler can not figure out which one of the return types to use. We are not there yet.
A plus of using trailing return type is that all function names beautifully align because their definitions all start with auto. The eye does not have to bounce around and do unnecessary lookups for the start of the next function. I'm kind of obsessed with code alignment and this MSVC addon is ultra cool.
Witness the shortest explanation of Concepts ever:
Concepts are a way to specialize types and the relationships between them before we try to use them. Common way of thinking about concepts is "types about types" but that is not the full picture - the relationships between types part is equally important.
Essentially they are predicates on types. I mean actual compile-time predicates, they return bool - you can use them with static_assert.
The way it works is you combine few requirements of the input template classes and the template instantiation fails if the requirements are not met hopefully with a readable error message.
End of explanation. This is the main idea and now a few examples:
This is how we check relationships between types:
And this is how a concept definition looks like:
They are like mini template instantiations that return true if instantiation is successful. All examples are taken from Concepts: The Future of Generic Programming by Bjarne Stroustrup. It is an excellent read.
3. Reference collapsing
Probably you've seen this:
- A& & becomes A&
- A& && becomes A&
- A&& & becomes A&
- A&& && becomes A&&
The first column of the table (with A) is the reference qualifiers for the type of the parameter in the definition of the function we are calling and the second column are of the object we are calling it with.
The result is the underlying reference type of the input parameter when viewed from inside the function. The underlying part is important. By definition a input parameter is viewed as lvalue regardless of T&& (it has in fact a name). To trigger the behavior as rvalue we should use std::forward that will reveal its true nature. Check this wandbox example.
The logic is simple - rvalues(temporaries) will be allowed to remain semantically rvalues only when passed to T&& while lvalues will always remain lvalues (we don't want our lvalues to silently become temporaries).
The table it looks a like operator|| (logical or - those vertical bars to not look cool outside the IDE but monospaced fonts look weird on text) so you can think of && as true that will only be preserved if it is combined with another true.
4. No excuses this time