CodeNebula
C++20coreadvanced

[[no_unique_address]] Attribute (C++20 Addition)

C++20 · The Big Four · Compiler flag: -std=c++20

Explanation

Allows empty non-static data members to occupy no space, optimizing memory.

Real-World Scenario

Optimize memory in a tightly packed data structure for a game.

Quick Snippet

snippet.cpp
struct Empty {}; struct Data { [[no_unique_address]] Empty e; int x; };

Full Example

no_unique_address.cpp
// CodeNebula - C++20 Example: [[no_unique_address]] Attribute
// Compile: g++ -std=c++20 -Wall -Wextra -o example no_unique_address.cpp

// [[no_unique_address]] allows empty class members to occupy zero bytes,
// enabling the Empty Base Optimization (EBO) for member variables.
// Without it, even an empty class member takes at least 1 byte.

#include <iostream>
#include <functional>
#include <memory>

// An empty class (stateless allocator, comparator, etc.)
struct Empty {};

// Another empty class — a stateless deleter
struct DefaultDeleter {
    void operator()(int* p) const { delete p; }
};

// Without [[no_unique_address]]: empty member wastes space
struct WithoutAttribute {
    int value;
    Empty tag;      // takes at least 1 byte + padding
};

// With [[no_unique_address]]: empty member takes zero bytes
struct WithAttribute {
    int value;
    [[no_unique_address]] Empty tag;  // can overlap with padding
};

// Practical example: compressed pair (like boost::compressed_pair)
template <typename T, typename Tag>
struct CompressedPair {
    T first;
    [[no_unique_address]] Tag second;

    std::size_t total_size() const { return sizeof(*this); }
};

// Simulate unique_ptr with a stateless deleter
template <typename T, typename Deleter = DefaultDeleter>
struct SmartPtr {
    T* ptr = nullptr;
    [[no_unique_address]] Deleter deleter;

    explicit SmartPtr(T* p) : ptr(p) {}
    ~SmartPtr() { if (ptr) deleter(ptr); }

    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
};

// Stateful vs stateless functors
struct StatelessCompare {
    bool operator()(int a, int b) const { return a < b; }
};

struct StatefulCompare {
    int threshold;
    bool operator()(int a, int b) const { return (a - threshold) < (b - threshold); }
};

template <typename Compare>
struct Container {
    int data[4];
    [[no_unique_address]] Compare comp;
};

int main() {
    std::cout << "=== [[no_unique_address]] for EBO ===\n\n";

    // Size comparison
    std::cout << "sizeof(Empty):            " << sizeof(Empty) << "\n";
    std::cout << "sizeof(int):              " << sizeof(int) << "\n";
    std::cout << "sizeof(WithoutAttribute): " << sizeof(WithoutAttribute) << "\n";
    std::cout << "sizeof(WithAttribute):    " << sizeof(WithAttribute) << "\n\n";

    // Compressed pair saves space with empty types
    CompressedPair<int, Empty> p1{42, {}};
    CompressedPair<int, int>   p2{42, 10};
    std::cout << "CompressedPair<int, Empty> size: " << p1.total_size() << "\n";
    std::cout << "CompressedPair<int, int>   size: " << p2.total_size() << "\n\n";

    // Smart pointer with stateless deleter
    SmartPtr<int> sp(new int{99});
    std::cout << "SmartPtr<int, DefaultDeleter> size: " << sizeof(sp) << "\n";
    std::cout << "Raw pointer size:                   " << sizeof(int*) << "\n";
    std::cout << "SmartPtr value: " << *sp << "\n\n";

    // Container with stateless vs stateful comparator
    Container<StatelessCompare> c1{{1,2,3,4}, {}};
    Container<StatefulCompare>  c2{{1,2,3,4}, {5}};
    std::cout << "Container<StatelessCompare> size: " << sizeof(c1) << "\n";
    std::cout << "Container<StatefulCompare>  size: " << sizeof(c2) << "\n";

    return 0;
}

// Expected output (typical 64-bit platform):
// === [[no_unique_address]] for EBO ===
//
// sizeof(Empty):            1
// sizeof(int):              4
// sizeof(WithoutAttribute): 8
// sizeof(WithAttribute):    4
//
// CompressedPair<int, Empty> size: 4
// CompressedPair<int, int>   size: 8
//
// SmartPtr<int, DefaultDeleter> size: 8
// Raw pointer size:                   8
// SmartPtr value: 99
//
// Container<StatelessCompare> size: 16
// Container<StatefulCompare>  size: 20
Run command
g++ -std=c++20 -Wall -Wextra -o example no_unique_address.cpp