Modern C++ Features – Quality-of-Life Features
source link: https://www.tuicool.com/articles/hit/Y32An22
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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 oflambdas, 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 :wink:
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!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK