static members

A member function should be static, if it doesn’t engage with the state of the object.

#include <compare>

class [[nodiscard]] r8 final {
public:
using uint8 = std::uint8_t;

private:
uint8 m_data{};

public:
r8() = default;
explicit r8(const uint8 n) : m_data(n) {}

uint8 loNibble() const noexcept { return m_data & 0b0000'1111; }
uint8 hiNibble() const noexcept { return (m_data & 0b1111'0000) >> 4; }

r8 &operator++() noexcept;
r8 operator++(int) noexcept;

r8 &operator--() noexcept;
r8 operator--(int) noexcept;

auto operator<=>(const r8 &) const = default;

[[nodiscard]] uint8 data() const noexcept { return m_data; }

static constexpr uint8 min() noexcept { return 0b0000'0000; } // <-
static constexpr uint8 max() noexcept { return 0b1111'1111; } // <-
};

min()/max() members are static, because these facts independent from state of the object. Both members are constexpr, because returned values known at compile time. No instance of r8 is needed to reach these information:

static_assert(r8::min() == 0);
static_assert(r8::max() == 255);

Same members could’ve been put using static constexpr variables:

class r8 {
// ...
static constexpr uint8 min = 0b0000'0000;
static constexpr uint8 max = 0b1111'1111;
};
static_assert(r8::min == 0);
static_assert(r8::max == 255);

Because we query something about the state, I optionated on function members. std::numeric_limit class example of this.


The keyword static kind of overloaded. When used on global scope functions/variables, it resembles anonymous namespace. When used on function scope variables, they preserve their values on each call:

static int k = 0;
static int f() {
static int i = 0;
return ++i;
}
namespace {
int k = 0;
int f() {
static int i = 0;
return ++i;
}
}
int main() {
assert(f() == 1);
assert(f() == 2);
assert(f() == 3);
}