On my previous post about C++ Policies, a policy-based design was used for comparators and loggers, which are stateless function objects (they only call a single function). These types are empty, but as members they still occupy space because C++ guarantees distinct addresses for distinct subobjects. With C++20’s [[no_unique_address]] or Empty Base Optimization (EBO), programmers can make these truly zero-cost, which is huge for small, hot objects like iterators or adapters.1

Minimal comparator policy

Generate a policy:

// Comparator policy
struct Less
{
  bool operator()(int a, int b) const noexcept
  {
    return a < b;
  }
};

Use the policy:

// A holder that uses a comparator policy
template<class Comp = Less>
struct MinOfTwo
{
  int  a, b;
  Comp comp; // empty, but as a member it costs at least 1 byte

  int  get() const
  {
    return comp(a, b) ? a : b;
  }
};

Use the policy with [[no_unique_address]]:

// A holder that uses a comparator policy with C++20 [[no_unique_address]]
template<class Comp = Less>
struct MinOfTwo_NUA
{
  int                        a, b;
  [[no_unique_address]] Comp comp; // empty and free

  int                        get() const
  {
    return comp(a, b) ? a : b;
  }
};

On typical x86‑64 ABIs, the MinOfTwo_NUA version often collapses to just the two ints (8 bytes), whereas the plain version is usually 12–16 bytes due empty member taking “extra space” (1 byte plus padding, with exact sizes ABI/implementation dependent). With [[no_unique_address]], that “extra space” can be overlaid with other members, reducing object size.

This can be seen on my examples -repo where I have following static_assert and the build compiles:

static_assert(sizeof(MinOfTwo<Less>) > sizeof(MinOfTwo_NUA<Less>), "MinOfTwo_NUA must be smaller than MinOfTwo");

When compiled with:

// This fails to compile: MinOfTwo_NUA is smaller
static_assert(sizeof(MinOfTwo<Less>) < sizeof(MinOfTwo_NUA<Less>), "MinOfTwo_NUA must be smaller than MinOfTwo");

the comparison reduces to (12 < 8) error is received, which indicates that MinOfTwo is 12 bytes, whereas MinOfTwo_NUA is 8 bytes on my x86‑64 system.

Pre-C++20 alternative

Before C++20, the Empty Base Optimization (EBO) achieves the same effect by inheriting from the empty policy:

// A holder that uses a comparator policy with Empty Base Optimization (EBO)
template<class Comp = Less>
struct MinOfTwo_EBO: private Comp
{
  int a, b;
  using Comp::operator();

  int get() const
  {
    return (*this)(a, b) ? a : b;
  }
};

Again same type of “collapsing” can be seen with EBO, this compiles:

static_assert(sizeof(MinOfTwo<Less>) > sizeof(MinOfTwo_EBO<Less>), "MinOfTwo_EBO must be smaller than MinOfTwo");

When compiled with:

static_assert(sizeof(MinOfTwo<Less>) < sizeof(MinOfTwo_EBO<Less>), "MinOfTwo_EBO must be smaller than MinOfTwo");

the comparison reduces to (12 < 8) is received again.

With EBO, an empty base class can be merged into the derived class layout, allowing the compiler to omit its storage entirely. This works because base classes don’t require distinct addresses like data members do, so the empty base can be overlaid with other members.

When to use which

  • Prefer [[no_unique_address]] for clarity with member-style composition (C++20 and forward)
  • Use EBO as a portable technique for pre-C++20

-sorhanp

References