Avoiding repetition of const and non-const version of getters?

150 Views Asked by At
struct BananaHolder
{
    vector<Banana>& getBananas();
    const vector<Banana>& getBananas() const;
};

My classes are cluttered with this kind of duplication.

Is there a cleaner, more elegant alternative?

2

There are 2 best solutions below

0
Andy Prowl On BEST ANSWER

If your class has to return references, and if it has to return a reference to a modifiable vector when invoked on a modifiable object, then I do not think there is a way to avoid the duplication - at least, not the duplicate declaration.

Notice, that some types of the Standard Library also suffer from the same problem. For instance, sequence containers such as std::vector have both a const and a non-const overload of operator [], at(), back(), front(), data(), and so on.

0
Jan Schultke On

In C++23, you can solve this issue with explicit object parameters (aka deducing this):

struct BananaHolder
{
    vector<Banana> m_bananas;
    
    // using abbreviated function templates
    auto& getBananas(this auto&& self) {
        return self.m_bananas;
    }
    
    // or more verbosely
    template <typename Self>
    auto& getBananas(this Self&& self) {
        return self.m_bananas;
    }
};

This not only covers const BananaHolder and BananaHolder, it covers all combinations of const, volatile, lvalue reference, and rvalue reference qualifications.

If you wanted to make full use of this, you would write:

// to get the same behavior without templates, we would need 16 overloads
template <typename Self>
auto&& getBananas(this Self&& self) {
    // without std::forward, this would always return an lvalue reference 
    return std::forward<Self>(self).m_bananas;
}

Prior to C++23, there is no way to avoid having at least two member functions. You would have to write two getter overloads like you did, for example.

For something more complicated than a getter, see How do I remove code duplication between similar const and non-const member functions?.