Wolpertinger

C++ Core Guidelines: Rules for Unions

A union is a particular data type where all members start at the same address. A union can hold only one type at a time; therefore, you can save memory. A tagged union is a union that keeps track of its types.

 

Wolpertinger

Here are the four rules for unions.

Let’s start with the most obvious rule.

C.180: Use unions to save memory

Because a union can hold only one type at one point at a time, you can save memory. The union will be as big as the biggest type.

 

union Value {
    int i;
    double d;
};

Value v = { 123 };      // now v holds an int
cout << v.i << '\n';    // write 123
v.d = 987.654;          // now v holds a double
cout << v.d << '\n';    // write 987.654

 

 

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.

     

    Value is a “naked” union. You should not use it according to the next rule.

    C.181: Avoid “naked” unions

    “Naked” unions are error-prone because you must keep track of the underlying type.

    // nakedUnion.cpp
    
    #include <iostream>
    
    union Value {
        int i;
        double d;
    };
    
    int main(){
      
      std::cout << std::endl;
    
      Value v;
      v.d = 987.654;  // v holds a double
      std::cout << "v.d: " << v.d << std::endl;     
      std::cout << "v.i: " << v.i << std::endl;      // (1)
    
      std::cout << std::endl;
    
      v.i = 123;     // v holds an int
      std::cout << "v.i: " << v.i << std::endl;
      std::cout << "v.d: " << v.d << std::endl;      // (2)
      
      std::cout << std::endl;
    
    }
    

     

     The union holds a double in the first iteration and an int value in the second iteration. If you read a double as an int (1) or an int as a double (2), you get undefined behavior.

    nakedUnion

     To overcome this source of errors, you should use a tagged union.

    C.182: Use anonymous unions to implement tagged unions

    Implementing a tagged union is quite sophisticated. In case you are curious, have a look at rule C.182. I will just make it easy and will write about the new C++ standard.

    With C++17, we get a tagged union: std::variant. std::variant is a type-safe union. Here is a first impression.

     

    // variant.cpp
    
    #include <variant>
    #include <string>
     
    int main(){
    
      std::variant<int, float> v, w;       // (1)
      v = 12;                              // v contains int
      int i = std::get<int>(v);            // (2)        
                                           
      w = std::get<int>(v);                // (3)
      w = std::get<0>(v);                  // same effect as the previous line
      w = v;                               // same effect as the previous line
    
                                           // (4)
      //  std::get<double>(v);             // error: no double in [int, float]
      //  std::get<3>(v);                  // error: valid index values are 0 and 1
     
      try{
        std::get<float>(w);                // w contains int, not float: will throw
      }
      catch (std::bad_variant_access&) {}
     
                                           // (5)
      std::variant<std::string> v("abc");  // converting constructors work when unambiguous
      v = "def";                           // converting assignment also works when unambiguous
    
    }
    

     

    In (2), I define the two variants v and w. Both can have an int and a float value. Their initial value is 0. This is the default value for the first underlying type. v becomes 12. std::get<int>(v) returns the value using the type. Line (3) and the following two lines show three possibilities to assign the variant v the variant w. But you have to keep a few rules in mind. You can ask for a variant’s value by type or index. The type must be unique and the index valid (4). If not, you will get a std::bad_variant_access exception. If the constructor or assignment call is unambiguous, a conversion occurs. This is why it’s possible to construct a std::variant<std::string> with a C-string or assign a new C-string to the variant (5).

    C.183: Don’t use a union for type punning

    At first, what is type punning? Type punning is the possibility of a programming language intentionally subverting the type system to treat a type as a different type. One typical way to do type punning in C++ is to read the member of a union with a different type from the one with which it was written.

    What is wrong with the following function bad?

    union Pun {
        int x;
        unsigned char c[sizeof(int)];
    };
    
    void bad(Pun& u)
    {
        u.x = 'x';
        cout << u.c[0] << '\n';       // undefined behavior (1)
    }
    
    void if_you_must_pun(int& x)
    {
        auto p = reinterpret_cast<unsigned char*>(&x);   // (2)
        cout << p[0] << '\n';                            // OK; better 
    // ...
    }

     

    Expression (1) has two issues. First and foremost, it’s undefined behavior. Second, the type punning is quite challenging to find. If you have to use type punning, do it with an explicit cast such as reinterpret_cast in (2). With reinterpret_cast you have at least the possibility to spot your type punning afterwards.

    What’s next?

    Admittedly, this final post on rules for classes and class hierarchies was a bit short. with the next post, I will write about the next significant section: enumerations.

     

     

     

     

    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

    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 *