01 implicit conversions

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.

 

01 implicit conversions

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.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • 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)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *