autoGetType

auto-matically inititialized

Probably the most frequently used feature of C++11 is auto. Thanks to auto, the compiler determines the type of a variable from its initializer. But what is the point of safety-critical software? 

 

The facts about auto

Automatic type deduction with auto is exceptionally convenient. First, you save a lot of unnecessary typing, particularly with challenging template expressions; second, the compiler is never wrong – in opposition to the programmer. 

I compare, in the following listing, the explicit and the deduced types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <vector>

int myAdd(int a,int b){ return a+b; }

int main(){

  // define an int-value
  int i= 5;                                  // explicit
  auto i1= 5;                                // auto
 
  // define a reference to an int
  int& b= i;                                 // explicit
  auto& b1= i;                               // auto
 
  // define a pointer to a function
  int (*add)(int,int)= myAdd;               // explicit
  auto add1= myAdd;                         // auto
  
  // iterate through a vector
  std::vector<int> vec;
  for (std::vector<int>::iterator it= vec.begin(); it != vec.end(); ++it){} 
  for (auto it1= vec.begin(); it1 != vec.end(); ++it1) {}

}

 

The compiler uses the rules for template argument deduction to get the variable type. Therefore, the outer const or volatile qualifier and references are removed. The following example shows this behavior for constant and references. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(){
  
  int i= 2011;
  const int i2= 2014;
  const int& i3= i2;
  
  auto a2= i2;     // int
  auto a3= i3;     // int
  
}

 

Rainer D 6 P2 500x500

 

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.

     

    But how can I be sure that a2 or a3 is of type int, although I used a variable of type const int or const int& to initialize them? Sometimes I deduce it wrong. The answer is simple. The compiler knows the truth. The only declared class template GetType helps me a lot. 

    template <typename T>
    class GetType; 
    

     

    The compiler will immediately complain if I use only the declared class template. The definition is missing. That is the characteristic I need. The compiler tells me exactly the type of class template, which can not be instantiated. At first, to the extended source code. I disabled the following source code the try to instantiate the only declared class template.

     

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    #include <get_type.hpp>
    
    int main(){
      
      int i= 2011;
      const int i2= 2014;
      // GetType<decltype(i2)> myType;
      const int& i3= i2;
      // GetType<decltype(i3)> myType;
      
      auto a2= i2; 
      // GetType<decltype(a2)> myType;
      auto a3= i3;
      // GetType<decltype(a3)> myType;
      
    }
    

     

    The GetType call in lines 7,9, 12, and 14 uses the specifier decltype, which gives you the exact type of the declared variable. The rest is only hard work. I successively commented on each GetType expression. A deep look into the error messages of the g++ compilers is very interesting.

     

     autoGetType

     

    The critical expressions of the error message have a red line. Impressed? But once more. What is the point of safety-critical software?

    Initialize me!

    auto determines its type from an initializer. That means. Without an initializer, there is no type and, therefore, no variable. To say it positively. The compiler takes care that each type is initialized. That is a nice side effect of auto that is mentioned too rarely. 

    Whether you forgot to initialize a variable or didn’t make it because of a wrong understanding of the language makes no difference. The result is simply the same: undefined behavior. With auto you can overcome these nasty errors. Be honest. Do you know all rules for the initialization of a variable? If yes, congratulations. If not, read the article’s default initialization and all referenced articles in this article. I have no clue why they used the following statement: “objects with automatic storage duration (and their subobjects) are initialized to indeterminate values”. This formulation causes more harm than good. Local variables will not be default initialized.

    I modified the second program of default initialization to make the undefined behavior more obvious.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // init.cpp
    
    #include <iostream>
    #include <string>
     
    struct T1 {};
     
    struct T2{
        int mem;     // Not ok: indeterminate value
     public:
        T2() {} 
    };
     
    int n;          //  ok: initialized to 0
     
    int main(){
      
      std::cout << std::endl;
      
      int n;               // Not ok: indeterminate value
      std::string s;       // ok: Invocation of the default constructor; initialized to "" 
      T1 t1;               // ok: Invocation of the default constructor 
      T2 t2;               // ok: Invocation of the default constructor
        
      std::cout << "::n " << ::n << std::endl;
      std::cout << "n: " << n << std::endl;
      std::cout << "s: " << s << std::endl;
      std::cout << "T2().mem: " << T2().mem << std::endl;
      
      std::cout << std::endl;
                          
    }
    

     

    At first, to the scope resolutions operator:: in line 25. :: addresses the global scope. In our case, the variable n in line 14. Curiously enough, the automatic variable n in line 25 has the value 0. n has an undefined value, and therefore the program has undefined behavior. That will not hold for the variable mem of the class T2. mem returns an undefined value.

     init

    Now, I rewrite the program with the help of auto.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // initAuto.cpp
    
    #include <iostream>
    #include <string>
     
    struct T1 {};
     
    struct T2{
        int mem= 0;  // auto mem= 0 is an error
     public:
        T2() {}
    };
     
    auto n= 0;
     
    int main(){
      
      std::cout << std::endl;
      
      using namespace std::string_literals;
      
      auto n= 0;
      auto s=""s;      
      auto t1= T1();               
      auto t2= T2();
        
      std::cout << "::n " << ::n << std::endl;
      std::cout << "n: " << n << std::endl;
      std::cout << "s: " << s << std::endl;
      std::cout << "T2().mem: " << T2().mem << std::endl;
      
      std::cout << std::endl;
                          
    }
    

     

    Two lines in the source code are especially interesting. First, line 9. The current standard forbids it to initialize non-constant members of a class with auto. Therefore, I have to use the explicit type. This is, from my perspective, contra-intuitive. Here is a discussion of the C++ standardization committee about this issue: article 3897.pdf. Second, line 23. C++14 gets C++ string literals. You build them by using a C string literal (“”) and adding the suffix s (“”s). For convenience, I imported the in line 20: using namespace std::string_literals

    The output of the program is not so thrilling, only for completeness. T2().mem has the value 0. 

     initAuto

    Refactorization

    Just now, I want to conclude the post a new use case of auto comes to my mind. auto very well supports the refactorization of your code. First, it’s very easy to restructure your code if there is no type of information. Second, the compiler automatically takes care of the right types. What does that mean? I answer in the form of a code snippet. At first, the code was without auto.

    int a= 5;
    int b= 10;
    int sum=  a * b * 3;
    int res= sum + 10; 
    

     

    When I replace the variable b of type in with a double 10.5, I have to adjust all dependent types. That is laborious and dangerous. I have to use the right types and take care of narrowing and other intelligent phenomenons in C++.

     

    int a2= 5;
    double b2= 10.5;
    double sum2= a2 * b2 * 3;
    double res2= sum2 * 10.5;
    

     

    This danger is not present in the case of auto. Everything happens auto-matically.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // refactAuto.cpp
    
    #include <typeinfo>
    #include <iostream>
    
    int main(){
      
      std::cout << std::endl;
    
      auto a= 5;
      auto b= 10;
      auto sum=  a * b * 3;
      auto res= sum + 10; 
      std::cout << "typeid(res).name(): " << typeid(res).name() << std::endl;
      
      auto a2= 5;
      auto b2= 10.5;
      auto sum2= a2 * b2 * 3;
      auto res2= sum2 * 10;  
      std::cout << "typeid(res2).name(): " << typeid(res2).name() << std::endl;
      
      auto a3= 5;
      auto b3= 10;
      auto sum3= a3 * b3 * 3.1f;
      auto res3= sum3 * 10;  
      std::cout << "typeid(res3).name(): " << typeid(res3).name() << std::endl;
      
      std::cout << std::endl;
       
    }
    

     

    The slight variations of the code snippet always determine the right type of res, res2, or res3. That’s the job of the compiler. The variable b2 in line 17 is of type double and therefore also res2; the variable sum3 in line 24 becomes due to the multiplication with the float literal 3.1f a float type and the final result res3. To get the type from the compiler, I use the typeid operator defined in the header typeinfo.

    Here you get the results in black on yellow.

     

     autoRefact

    Impressed? Me too.

    What’s next?

    The initialization with curly braces {} has much in common with auto. It is used similarly often, helps to read the code, and makes your code safer. How? You will see it in the next post.

     

     

     

    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, Matt Godbolt, and Honey Sukesan.

    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 *