Contents
With the new C++ standards, we got a lot of features that feel like “quality-of-life” features. They make things easier for the programmer but do not add functionality that wasn’t already there. Except, some of those features do add functionality we couldn’t implement manually.
Some of those quality-of-life features are really exactly that. The standard often describes them as being equivalent to some alternative code we can actually type. Others are mostly quality-of-life, but there are edge cases where we can not get the effect by hand, or the feature is slightly superior to the manual implementation.
I will concentrate on core language features here, since most library features are implementable using regular C++. Only a few library features use compiler intrinsics.
auto type deduction
Auto type deduction for variables is one of the features that are mostly quality-of-life functionality. In all but a few cases, we theoretically would be able to write out the type of the variable. It would require some tedious typing and, in some places, the use of decltype
.
In a few cases, however, we can not possibly write down the type, so auto
has no alternative. The case that comes to mind here is the use of lambdas, whose type is nothing we can type:
auto lambda = [](){ return "Hello, lambda!"s; };
Here, decltype
can’t help us, either. We would have to write the lambda expression twice, and each of those expressions would create a different type.
By the way, type deduction for function return types is not a quality-of-life feature, neither are trailing return types.
Range-based for loops
Range-based for loops are a pure quality of life feature. The corresponding section in the standard explicitly says (in more general notation), that for (decl : rng){ ... }
is equivalent to
{
auto && __range = rng;
auto __begin = begin(__range);
auto __end = end(__range) ;
for ( ; __begin != __end; ++__begin ) {
decl = *__begin;
...
}
}
Of course, the actual wording is a bit more language-lawyerish, and there are a few distinctions about __begin
and __end
but it’s nothing we couldn’t type.
Defaulted and deleted functions
At first sight, explicitly defaulted functions are a quality-of-life feature. Only a few special member functions can be explicitly defaulted, and the effect can be implemented by hand. However, a manually implemented function is considered as user-declared by the standard, whereas a function that has been explicitly defaulted at its first appearance is not. In turn, having user-declared constructors or not influences whether a type is considered an aggregate, which has further implications. Welcome to the foxholes and dusty corners of the language 😉
Explicitly deleting a function means it takes place in overload resolution, but the compilation fails when that overload would be selected. We could have a similar effect by declaring but not implementing the function, but in that case, we would get an error at link time, which is different. So, explicitly deleted functions are more than a quality-of-life feature.
Structured bindings
C++17’s structured bindings are a pure quality-of-life feature. The wording of the standard makes it clear that we could implement all that is done in that feature by hand. It would be done in terms of std::get<i>
, std::tuple_element
etc. It would be extremely tedious though, especially getting the types of the referenced struct/tuple members right.
nullptr
nullptr
could be considered a library feature, since its type, std::nullptr_t
is a normal library class. That would make it a pure quality-of-life feature. However, nullptr
is a keyword and therefore part of the language itself. In addition, it is explicitly mentioned in the standard when it comes to null pointer conversions, which may have further implications. Therefore I’d consider it mostly quality-of-life, but with a special place in the language lawyers’ hearts.
Inheriting and delegating constructors
Inheriting constructors are quality-of-life functionalities in the sense that we could write constructors by hand that do nothing else but calling base class constructors. However, those constructors would require forwarding the parameters of the derived constructor to the base constructor. That can be optimized away but is not strictly the same as directly using the base constructor.
In addition, with C++17 we got the possibility of inheriting the constructors of a variadic number of base classes. This can not be done manually at all:
template <class... Bases>
class Derived : public Bases... {
public:
using Bases::Bases...;
};
(Don’t try this at home. Except for Clang I have found no compiler where you actually can use this.)
Delegating constructors are more than a quality of life feature. For example, we may have public constructors that delegate to private constructors, which can not be emulated otherwise.
Conclusion
There are lots of features where people may ask why they were added to the language since they only add syntactic sugar and complexity to an already complex language. However, if we look closely, they very often add more than that. Besides that, syntactic sugar is what makes our code more readable and maintainable.
Do you know more new standard features that are purely quality-of-life or slightly more than that? Please leave a comment!
Permalink
You can indeed write down the type of the lambda as soon as you define the struct which implicitly created yourself, right?
So I think this is not a proper example for more-than-qol use case of auto, its just quite a hassle to do it without auto..
Permalink
If you manually write a struct that does the same thing a lambda would do, then you can write down the type of that struct, yes. But that’s not the same type as the type of the lambda. If you’d write the exact same lambda expression twice in the same line of code, they’d still have two different, unpronounceable types.
auto
is the only way to get a second object of the same lambda type.Permalink
VS 2017 15.8.4 compiles the snippet about “inheriting the constructors of a variadic number of base classes.”
Permalink
Thanks! I guess GCC and others will compile things like that in a not too far future as well 😉
Permalink
I’m looking forward to ranges, which are definitely not “just” a quality-of-life feature! Right now I have to do manual memory management to be able to return a buffer that is consecutive in memory but with ranges that wouldn’t be necessary anymore.
Permalink
The use of ‘override’ has definitely improved my Quality Of Life 🙂
Permalink
Hey Bart, absolutely. But it’s more than the “qol” definition I use here since it’s not just saving us some typing but also the search for that damn spot where we used a slightly different signature 😉