# C++ quick tips: Member functions with reference qualifier


Have you ever used member functions with reference qualifiers?  This a feature
introduced in C++11, being an essential part of move semantics.

<!--more-->

## Short recap

C++11 allows for specifying either *lvalue* or *rvalue* reference qualifier on
a non-static member function.  The reference qualifier is part of an overload
resolution so, it's possible to have the following:

```c++
class Box {
public:
    void abc() & {
        std::cout << "lvalue variant" << std::endl;
    }

    void abc() && {
        std::cout << "rvalue variant" << std::endl;
    }
};

Box foo() {
    return Box{};
}

int main() {
    Box b;

    // lvalue
    b.abc();

    // rvalues
    std::move(b).abc();
    Box{}.abc();
    foo().abc();

    return 0;
}
```

## Move semantics is not only for function arguments

Now, when would that be of any use?  Imagine a data container:

```C++
class Box {
public:
    const std::vector<int>& getData() const {
        return lots_of_data;
    }

private:
    std::vector<int> lots_of_data;
};

int main() {
    Box b;
    auto data = b.getData();
    return 0;
}
```

You often provide read only access as an optimal solution, preventing copying
the vector.  There's nothing wrong with that but now, it's possible to
complement that with move semantics:

```C++
class Box {
public:
    // overload #0
    const std::vector<int>& getData() const & {
        return lots_of_data;
    }

    // overload #1
    std::vector<int> getData() & {
        return lots_of_data;
    }

    // overload #2
    std::vector<int> getData() && {
        return lots_of_data;
    }

private:
    std::vector<int> lots_of_data;
};

void use(const Box& b) {
    // will use overload #0
    b.getData();
}

int main() {
    Box b;

    use(b);

    // will use overload #1
    auto copy = b.getData();

    // will use overload #2, b is now an empty husk
    auto data = std::move(b).getData();

    return 0;
}
```

I've seen this trick for the first time used in Meta's Folly library's Future implementation[^1].

In short, you use the *rvalue* overload to convey that the data is gonna be
moved **out** therefore, you relinquish the ownership, transferring it via the
return value to the caller.

## C++23 and explicit object parameter

C++23 introduces an interesting feature which is somewhat related to member
function's reference qualifiers - *explicit object parameter*[^2].  Here's a short overview:

```C++
class Box {
public:
    // This is the same as
    // const std::vector<int>& getData() const &;
    const std::vector<int>& getData(this const Box& b) {
        return b.lots_of_data;
    }

private:
    std::vector<int> lots_of_data;
};
```

It allows for naming `this` explicitly within the member functions.  *this* is
no longer an implicit variable in such function.  Object data access has to
happen through the object variable (which now is a reference, not a pointer).

By default named `this` is now a reference but it's possible to pass the object
by value (very much similar to value receivers in golang) to member functions:

```C++
    std::vector<int> getData(this Box b) {
        // b is a copy here
        // local modifications don't affect the original object
        b.lots_of_data.push_back(123);
        return b.lots_of_data;
    }
```

But what's the benefit of having that?  Well, you can now have a common function handling all three overloads:

```C++

    template <typename Self>
    auto getData(this Self&& self) {
        if constexpr (std::is_rvalue_reference_v<Self&&>) {
            std::cout << "move" << std::endl;
            return std::move(self.lots_of_data);
        } else if (std::is_same_v<Self&&, const Box&>) {
            std::cout << "constref" << std::endl;
            return self.lots_of_data;
        } else {
            std::cout << "copy" << std::endl;
            return self.lots_of_data;
        }
    }

```
Or even more conveniently, just drop the template and use `auto`:

```C++
    auto getData(this auto&& self) {
        using Self = decltype(self);
        if constexpr (std::is_rvalue_reference_v<Self&&>) {
            std::cout << "move" << std::endl;
            return std::move(self.lots_of_data);
        } else if (std::is_same_v<Self&&, const Box&>) {
            std::cout << "constref" << std::endl;
            return self.lots_of_data;
        } else {
            std::cout << "copy" << std::endl;
            return self.lots_of_data;
        }
    }

```

[^1]: [Folly future](https://github.com/facebook/folly/blob/v2025.07.21.00/folly/futures/Future.h#L1350)
[^2]: [explicit object parameter](https://en.cppreference.com/w/cpp/language/member_functions.html#Explicit_object_member_functions)

