uninitialized_tag in C++

No doubt, C++ is one of those languages you can use to squeeze out every last drop of your CPU’s processing power. On the other hand, it also allows a high amount of abstraction. However, micro-optimization seldom works well with nice abstractions.

The dilemma

One such case is the matter of default-initialization with “math types”, such as three-dimensional vectors used in computer graphics. Do you let your default constructor zero initialize by default or do you leave the elements uninitialized and risk undefined behavior?

One way around this dilemma is to use tag dispatching to enable both:

template <class T> struct v3 {
  v3(T v={}) : x{v}, y{v}, z{v} {}
  v3(uninitialized_tag) {}
  T x,y,z;
};

Now a v3 zero-initializes by default, while you can still avoid the initialization costs by calling it with:

v3<float>{uninitialized_tag{}};

Drawbacks

This approach is not without drawbacks. It’s a bit of an uphill battle to find a good test for this. You need to overwrite the values before you use them, or the compiler is free to do whatever it wants when you use them. It’d be undefined behavior. But you also do not want it to figure out that you are overwriting all the values – because in that case, it can optimize out the zero-initialization anyways.
It does work for a few simple cases though, and you scan see the zero-initialization getting removed, e.g. in the compiler explorer.

However, it will often not let you do what you set out to do – leave some some vectors uninitialized. Consider this:

std::vector<v3<float>> v(N, uninitialized_tag{});

This does not, in fact, transport the uninitialized_tag to the v3 constructor. It first converts the tag to a v3, and then uses that value to initialize all the other N elements with the uninitialized data. This is actually a lot of copying, and creates a whole lot more code than the zero initialization would have. You can get this to work with a container that uses the given initializer value to initialize the elements without converting first. You’re probably better of with a mechanism like the std::vector::reserve that essentially gives you the ability to leave elements uninitialized.

Conclusion

This is a very specialized method for very few niche cases, and you need to carefully select your infrastructure to see any gain that cannot be achieved by simpler means. Use with caution!

10 thoughts on “uninitialized_tag in C++”

  1. I think here:

    std::vector<v3> v{N, uninitialized_tag{}};

    You intended to write:

    std::vector<v3> v ( N, uninitialized_tag{} );

    Otherwise std::vector is made from initializer_list.

  2. For testing it, you could use placement new:

    // Setup memory
    char buf[ BUFSIZ ];
    const auto fillVal = char(0xa5);
    std::fill( std::begin(buf), std::end(buf), fillVal );

    // Call the ctor
    const auto v = new( buf ) v3{ uninitialized_tag{} };

    // Validate the results
    const auto intVal = int(0xa5a5a5a5);
    assert( v->x == intVal );
    assert( v->y == intVal );
    assert( v->z == intVal );

    1. And of course it dropped the template tags. It should have been:

      const auto v = new( buf ) v3<int>{ uninitialized_tag{} };

  3. I think the example in the “Drawbacks” section might be undefined behaviour as it results in reads from uninitialized memory.

    1. Because the uninitialized values are copied by the vector internally and hence read, even if you do not read them afterwards? Maybe. I’m actually not sure. Maybe someone else can enlighten us?

      1. Hmm, I’m relatively sure it is. Reminded me of this talk:

        There the guy explains (shortly after the 19 minute mark) how reading uninitialized memory can cause unexpected problems. I found this quite interesting (although I also found the style of the talk a bit importunate).

        Anyway, interesting post!

      2. There is no undefined behavior, and there is no copying. That vector constructor, first, allocates (using allocator) raw memory sufficient for N objects, and then does uninitialized_fill, i.e in-place construction (or memcpy in case of triviallycopiable type) — which in this case would be complitely optimized away. So the “Drawback” paragraph is incorrect.

      3. Well, I tried both gcc and clang, and neither optimized the copy away. But if you got an example where it does, I would be very interested in seeing that.

  4. How about using different classes depending on which construction behavior you prefer? (E.g., I’m thinking a base struct could be non-initializing by default, and derived struct adds zero-initialization; Or the choice of zero-init could be another template parameter).
    This way you could create a vector of uninitialized values if you wanted to.
    And it would make it more obvious which variables need extra care to avoid using them uninitialized.

    Of course there’d be more work needed if you wanted to be able to use both type variations interchangeably elsewhere, by providing appropriate conversion functions.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.