The C++ core guidelines support three profiles: type safety, bounds safety, and lifetime safety. Thanks to the guideline support library (GSL), you can check your code against these profiles. Today, I start with the profile type safety.
If you don’t know what a profile is, read my last post: C++ Core Guidelines: Profiles. Although the idea of a profile is it to target a specific goal, it needs assistance from the other profiles. This said, the type safety profile needs, for example, assistance from the bounds safety, and lifetime safety profile. Now, let’s dive into the type safety.
Type safety means that you use the types correctly and, therefore, avoid unsafe casts and unions. Type safety consists of eight rules which are called type. The rules start with don’t, always, or avoid and refer to existing rules. If necessary, I will add content to the rules.
static_castfor arithmetic types
Don’t cast between pointer types where the source type and the target type are the same
- Don’t cast between pointer types when the conversion could be implicit
The answer to this doesn’t boil down to two points:
Avoid in particular C-casts and prefer if necessarily named casts:
Let’s see what will happen if I screw up the type system.
Neither the result with the Visual Studio compiler
nor the result with the GCC or the Clang compiler is promising.
What is so bad about the C-cast? You don’t see which cast is performed. If you use a C-cast, a combination of casts will be applied if necessary. Roughly speaking, a C-cast starts with a static_cast, continues with a const_cast, and finally performs a reinterpret_cast.
There is another issue I don’t like about a C-cast. It’s challenging to find C-casts in your source code. This remark does not hold for C++ casts such as dynamic_cast, const_cast, static_cast, or reinterpret_cast.
Of course, you know how I will continue: explicit is better than implicit.
Prefer named C++-casts
Including the GSL, C++ offers eight different named casts.
Here are they including a short description:
- static_cast: conversion between similar types such as pointer types or numeric types
- const_cast: adds or removes const or volatile
- reinterpret_cast: converts between pointers or between integral types and pointers
- dynamic_ cast: converts between polymorph pointers or references in the same class hierarchy
- std::move: converts to an rvalue reference
- std::forward: converts to an rvalue reference
- gsl::narrow_cast: applies a static_cast
- gsl::narrow: applies a static_cast
I know the description is concise. I want to make a least two remarks:
- GSL stands for guideline support library and is a header-only library using the namespace gsl. The GSL can automatically check the rules of the C++ core guidelines and, in particular, the profiles. I will write about this check in a future post. I have already written a post to the GSL: C++ Core Guidelines: The Guidelines Support Library.
- What? std::move and std::forward are casts? Let’s have a closer look at the internals of std::move: static_cast<std::remove_reference<decltype(arg)>::type&&>(arg). First, the type of argument arg is determined by decltype(arg). Then all references are removed, and two new references are added. The function std::remove_reference is from the type-traits library. I have already written a few posts to the type-traits library. In the end, each argument arg to std::move comes out as an rvalue reference.
- Don’t use
I can give you a short answer: Use dynamic_cast instead. I have already written about this topic in my post C++ Core Guidelines: Accessing Objects in a Hierarchy.
- Don’t use
const_castto cast away
Let me be more specific. Casting away const is undefined behavior if the underlying object, such as constInt is not mutable.
If you don’t believe me, there is a footnote in the C standard [ISO/IEC 9899:2011] (subclause 6.7.3, paragraph 4) relevant to the C++ standard: The implementation may place a const object that is not volatile in a read-only region of storage. Moreover, the implementation need not allocate storage for such an object if its address is never used. This means that a modification of an original constant object may have no result.
- Don’t use C-style
The first part of this don’t is relatively easy to answer: prefer named casts such as in type 1.
The functional T(e) cast is used to construct T from the expression e. Let me show what can happen if you use the functional cast incorrectly.
The function f takes four arguments and uses these arguments for initializing char‘s. You get what you deserve and can only hope for a warning. C++ Insights shows explicitly how your code is transformed. A static_cast is applied to each argument.
This process is called narrowing conversion and should be detected by the compiler. Using curly braces instead of round braces checks if a narrowing conversion happens. The C++ compiler must write a warning but typically throws an error when this happens. If you want to be sure that narrowing conversion always triggers an error, treat narrowing warnings with GCC and Clang as an error with -Werror=narrowing. Here is the modified program using curly braces for initialization.
Now, the compiler detects what is going wrong.
In the next post, I will finish the rules to type safety. They are about initialization, unions, and varargs. Now, I have to prepare for a fascinating week in Aurora. I give a two-day concurrency workshop, a back-to-basics talk on concurrency, and a presentation on concepts at the CppCon.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, moon, and Philipp Lenk.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
|My special thanks to SHAVEDYAKS|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,