C++ Insights – Implicit Conversions
I’m totally happy to announce that this post starts a series of posts to C++ Insights. C++ Insights is an awesome tool that I use heavily in my posts and in my classes to show the magic of the C++ compiler.
This series is motivated by a brief conversation I had with Andreas. I asked him if he has some use case examples which show how C++ Insights can be helpful when teaching. I think there are many things. This article is the start of a series of five posts by Andreas which I will publish at Modernes C++ because I think C++ Insights is an invaluable tool to get a deeper insight into the C++ compiler magic. In case, you are new to C++ Insights consider this introductory article. Without further ado, Andreas post. When you follow the link near to each example, you can directly analyse the example in C++ Insight.
Implicit Conversions
Let’s start with something simple which happens so often: implicit conversations. Sometimes, they are perceived as cumbersome or hidden, sometimes as powerful. For beginners and even for experts in certain debugging situations it is hard to see where implicit conversations happen.
A Basic Example
Consider this basic example:
void UnsignedIntFunction(unsigned int) {}
int main()
{
int x = 1;
UnsignedIntFunction(x);
}
With these few lines and knowledge of C++, it is easy to see that UnsignedIntFunction
takes a unsigned int
while we are passing an int
. Aside from the fact that the two types have different ranges on the call side, it works without additional work. This truncation is harder to spot in a bigger codebase. For students, it is even harder in my experience. Using C++ Insights gives you the following output:
void UnsignedIntFunction(unsigned int)
{
}
int main()
{
int x = 1;
UnsignedIntFunction(static_cast<unsigned int>(x));
}
You can now show that and where the implicit conversion kicks in. The great benefit of C++ Insights as an online tool is that you can change the signature of UnsignedIntFunction
to int
and see the implicit conversion disappear. This is powerful as your students do not just have to believe you, they can see it and experiment with it themselves. I frequently tell my students not to trust me and challenge what I’m telling them. With C++ Insights they easily can. Sadly for me, they sometimes find my mistakes, but that is a different story.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
Classes
Let’s move on to something more complex like classes with conversion operators things get more tricky to spot. At this point, we added operator overloading to the picture. The question is, which overload gets selected when? There was a recent tweet by @walletfox illustrating how C++ Insights can help to show which overload std::cout
gets selected. Consider this, cout
-free example:
class Foo
{
public:
Foo(int v)
: mV{v}
{
}
Foo& operator+=(const Foo& rhs)
{
mV += rhs.mV;
return *this;
}
operator int() { return mV; }
private:
int mV;
};
int main()
{
Foo t(2);
t = 3 * 2;
Foo tt(4);
t += tt * 2;
}
The question is, do you directly spot what happens for t = 3 * 2
and t += tt * 2
? Running C++ Insights on it gives the following output:
class Foo
{
public:
inline Foo(int v)
: mV{v}
{
}
inline Foo & operator+=(const Foo & rhs)
{
this->mV += rhs.mV;
return *this;
}
using retType = int;
inline operator retType ()
{
return this->mV;
}
private:
int mV;
/* public: inline constexpr Foo(const Foo &); */
/* public: inline constexpr Foo(Foo &&); */
/* public: inline Foo & operator=(const Foo &); */
/* public: inline Foo & operator=(Foo &&) noexcept; */
/* public: inline ~Foo() noexcept; */
};
int main()
{
Foo t = Foo(2);
t.operator=(Foo(3 * 2));
Foo tt = Foo(4);
t.operator+=(Foo(static_cast<int>(tt.operator int()) * 2));
}
Aside from the question, you can see implicit member functions the compiler adds like copy and move operations as well as the destructor. You also see the answer to the original question; there are implicit conversions. Both times a temporary object of Foo
is created which then gets passed to operator=(const Foo&)
and operator+=(const Foo)
. We did not implement the first one. In addition to all this, the conversion operator operator int()
is also used to first convert a Foo
object to an int
just multiply it by 2 and then pass the result in a temporary Foo
object.
Special Member Functions
Another thing C++ Insights shows us, you have already seen it, are the special member functions the compiler generates for us. In the example above, we can see the copy and move constructor as well as the copy and move assignment operators. Here is an example demonstrating it even better:
class A
{
public:
A() = default;
A(const A&) {}
};
class B
{
public:
};
int main()
{
A a;
A a2;
//a = a2;
B b;
}
In class A
we do provide a copy constructor. With that, the compiler does no longer generate the move operations for this class as it does for B
:
class A
{
public:
A() = default;
inline A(const A &)
{
}
// public: inline constexpr A() noexcept;
};
class B
{
public:
// public: inline constexpr B() noexcept;
// public: inline constexpr B(const B &);
// public: inline constexpr B(B &&);
};
int main()
{
A a = A();
A a2 = A();
B b = B();
}
What you can see in addition is, that the special members are only generated if needed. In the code as it is, there is no assignment operator. However, if we enable the line a = a2
we get one:
class A
{
public:
A() = default;
inline A(const A &)
{
}
// public: inline constexpr A() noexcept;
// public: inline constexpr A & operator=(const A &) noexcept;
};
class B
{
public:
// public: inline constexpr B() noexcept;
// public: inline constexpr B(const B &);
// public: inline constexpr B(B &&);
};
int main()
{
A a = A();
A a2 = A();
a.operator=(a2);
B b = B();
}
I think that the power of C++ Insights is that you can see how a code change on your side affects what the compiler adds or selects. It’s more or less like the brilliant compiler explorer except for it spits the result out in a language we all understand well.
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 type deduction …
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 |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!