Imagine that you are writing a client application to send some data in segments of 10 characters along with a number that tells how many characters are changed (sending order does not matter!) to a server. How could we define a data structure for this! :thinking:

Maybe,

struct Data {
    std::array<char, 10> content;
    int num_chars_changed;
};

Since, we already know that the data will be received in segments of 10 characters, we can nicely use fixed size container, std::array which expects the array size as a compile-time constant. Then we declare an integer that tells how many characters are changed.

This is already good! We have a data structure that solves our need and is of size, 16 bytes (assuming an int takes 4 bytes in the machine). Is this data structure optimal in terms of memory? :thinking: Imagine our client needs to send 500 segments per second. Which means, we send 500 x 16 = 8000 bytes of data per second. Looks like, we are allocating more memory than required for our problem. Let us try to do some optimizations!!! :smirk:

We already know the value range of num_chars_changed, which is 0 to 10. The value can never be a negative number, so we could use an unsigned integer instead of a signed integer. Further more, we could use the smallest unsigned integer, which is uint8_t to accomodate the maximum value of this variable. Now, our data structure looks like this,

struct Data {
    std::array<char, 10> content;
    uint8_t num_chars_changed;
};

The size of the new data structure would be 11 bytes. For 500 data segments, this would account to 500 x 11 = 5500 bytes. We already did a great job by reducing 2500 bytes :smile:

What if! there are less frequent changes in the data. Then, we still end up sending 5500 bytes to the server. Is there a way, to even optimize out the num_chars_changed by only allocating memory if and only if there are changes to the data segment. Well, C++20 introduced an attribute for it, [[no_unique_address]].

According to cppreference, [[no_unique_address]] allows a data member to be overlapped with other non-static data members or base class subobjects of its class. Let us see this in action,

The modified structure looks as below,

struct EmptyType{};

struct Data {
    std::array<char, 10> content;
    [[no_unique_address]] EmptyType num_chars_changed;
};

Now, the size of the above structure is 10 bytes (just the size of the array). Here, the address of num_chars_changed is overlapped within the non-static member variable, content. How can we make use of this attribute to solve our problem - Only allocating memory to num_chars_changed if and only if there are changes to the content. Templates to the resuce!!! The final modified structure looks as below,

struct EmptyType{};

template <typename NumBits = uint8_t> // default type is uint8_t
struct Data {
    std::array<char, 10> content;
    [[no_unique_address]] NumBits num_chars_changed;

    bool isChanged() {
        return !std::is_empty_v<NumBits>;
    }
};

I also added a member function that tells if there are any changes in the content. It just checks if the type of num_chars_changed is empty type or not using std::is_empty_v. Now we can use the final structure to create segments of Data and also only allocating memory for num_chars_changed when necessary. The size of the structure would be 11 bytes if there are any changes to the content and 10 bytes when there are no changes. Following code also contains declerations of the structure for both the cases, updates and no updates to the content,

#include <iostream>
#include <array>
#include <type_traits>

struct EmptyType{};

template <typename NumBits = uint8_t>
struct Data {
    std::array<char, 10> content;
    [[no_unique_address]] NumBits num_chars_changed;

    bool isChanged() {
        return !std::is_empty_v<NumBits>;
    }
};

int main(int argc, char** argv) {
    Data<> updates{"lorem", 5};
    Data<EmptyType> no_updates{"lorem"};
    std::cout << "sizeof(updates): " << sizeof(updates) << "\n"; // outputs - 11
    std::cout << "sizeof(no_updates): " << sizeof(no_updates) << "\n"; // outputs - 10
    return 0;
}

You can see the output of the above program in this link. I know, it is not the best example, but it demonstrates the usage of [[no_unique_address]] nicely. Thanks again for reading :satisfied: !!!