C++ Insights – Type Deduction
Andreas Fertigs story with C++ Insights goes on. This weeks post is about type deduction with auto
and decltype
or as I often phrase it: “Use the smartness of the compiler.”
Type Deduction
With C++11 we got auto
and decltype
and, therefore, two new forms of type deduction. We are used to type deduction from templates, however, these two new variants can be tricky sometimes.
auto
Consider this example:
int main() { int* ip; const int* cip; const int* const cicp = ip; auto aip = ip; auto acip = cip; auto acicp = cicp; }
We have three different pointers, all of type int
. They are getting more and more constified. The question is what’s the type that is deduced by auto
? All are pointers, that’s for sure. But what happens with the const
? auto
removes all top-level qualifier. Hence, even the const
disappears. Does it? Here is the output C++ Insights gives:
int main()
{
int * ip;
const int * cip;
const int *const cicp = ip;
int * aip = ip;
const int * acip = cip;
const int * acicp = cicp;
}
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
Yes, the top-level const
is removed. A constant pointer does not matter, so this const
is discarded, but the constness of the memory behind it sticks. Hence, this const
is preserved. Which is why acip
looks exactly like acicp
. This makes sense right.
decltype(auto)
Now, from time to time we like to preserve all qualifiers. This is when decltype
appears. In contrast to auto
, decltype
does preserve all top-level qualifiers. With C++14 the combination of decltype
and auto
is possible and we can write decltype(auto)
which makes things easier. Here is another example from C++ Insights which uses C++14:
int main() { int* ip; const int* cip; const int* const cicp = ip; decltype(auto) aip = ip; decltype(auto) acip = cip; decltype(auto) acicp = cicp; }
From this we get the following output:
int main()
{
int * ip;
const int * cip;
const int *const cicp = ip;
int * aip = ip;
const int * acip = cip;
const int *const acicp = cicp;
}
We can see, that acicp
does carry the second const
which is lost when we just use auto
.
decltype
When do we need decltype
, or more precisely when do we like to keep all qualifiers? One popular reason is templates. Imagine a class template with some function Get. With just using auto
it as a return type we can never return a reference to something. In template code, we often don’t know the exact types, which makes it desirable to provide code that just works. decltype
can help here. However, consider decltype
a library writer feature. In most cases, we are fine with auto
. It is just good to know the entire toolbox.
auto& and auto* versus auto
What we‘ve seen so far is, that we need to be explicit, if it comes to auto
and references. We must always write auto&
to get a reference. How is it with pointers? There auto
gives us the correct type, so we can spare the star? This is in fact a question I frequently get from my students. The answer is: it depends. I recommend writing it just for consistency. However, there are scenarios where we indeed need auto* even that auto
did deduce the correct type. Consider this example:
struct Foo{}; Foo* GetFoo() { static Foo foo; return &foo; } int main() { auto fp = GetFoo(); }
We have a function returning a Foo*
and an auto
variable auto f = GetFoo()
which deduces the type. Of course, the correct type. What if we’d like to make f
const? That we cannot alter the data of f
? Sure, we write it like this const auto f = ...
. At least that’s what we would do if we write it without auto
. Here are some possibilities we can try:
struct Foo{}; Foo* GetFoo() { static Foo foo; return &foo; } int main() { auto fp0 = GetFoo(); const auto fp1 = GetFoo(); auto const fp2 = GetFoo(); //const auto const fp3 = GetFoo(); does not compile const auto* fp4 = GetFoo(); auto* const fp5 = GetFoo(); const auto* const fp6 = GetFoo(); }
First, fp1
produces a const pointer to mutable data. Not exactly what we intended. fp2
probably seems pointless. fp3
makes more sense, but this doesn’t compile. The control changes, if we start using the form auto*
. Now, we can add the qualifiers like we can do it with a regular type. But see for yourself in C++ Insights what the result is:
struct Foo{/* public: inline constexpr Foo() noexcept; */
/* public: inline constexpr Foo(const Foo &); */
/* public: inline constexpr Foo(Foo &&); */
};
Foo * GetFoo()
{
static Foo foo = Foo();
return &foo;
}
int main()
{
Foo * fp0 = GetFoo();
Foo *const fp1 = GetFoo();
Foo *const fp2 = GetFoo();
const Foo * fp4 = GetFoo();
Foo *const fp5 = GetFoo();
const Foo *const fp6 = GetFoo();
}
The simple advice is: always be explicit and use the form auto&
as well as auto*
even if auto is able to deduce a pointer type.
Let’s say we are explicit and use auto&
. Look at this example:
struct Singleton{}; auto Get() { static Singleton s{}; return s; } int main() { auto& x = Get(); }
We have a classical singleton which the function Get
should return. Of course, we like a reference to it otherwise we have multiple tons. Despite auto
and &
this code does not compile:
error: non-const lvalue reference to type 'Singleton' cannot bind to a temporary of type 'Singleton'
The reason is, the Get
in fact return Singleton
and not Singleton&
. Why? Because we did not apply the &
to the auto-return type of Get
. A small change and the code compiles:
struct Singleton{}; auto& Get() { static Singleton s{}; return s; } int main() { auto& x = Get(); }
I’d like to thank Rainer for the opportunity to share information about C++ Insights on his popular blog!
Have fun with C++ Insights. You can support the project by becoming a Patreon or of course with code contributions.
Stay tuned for more insights about C++ Insights to template instantiation …
Andreas
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, 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, Stephen Kelley, Kyle Dean, Tusar Palauri, 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, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.
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 |
Seminars
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
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++
- C++20
Contact Me
- Mobil: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!