4
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?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • Perhaps returning references all the time isn't really the best option. – chris Jul 06 '13 at 20:33
  • In my situation it is required. – Vittorio Romeo Jul 06 '13 at 20:34
  • @AndyProwl alright. Could you copy-paste that into an answer so that I can accept it? – Vittorio Romeo Jul 06 '13 at 20:43
  • @VittorioRomeo: Did that :) Deleting my previous comment... – Andy Prowl Jul 06 '13 at 20:54
  • One could go Standard Library route and create a template method function which accept banana holders and takes bananas out of them (I refer to `std::data`, etc). Except standard ones use existing getters, your function has to be a friend. But for a member function-getter? Before C++23 there is no way and C++23 way looks odd to me. – Swift - Friday Pie Sep 02 '23 at 20:53
  • You appear to overuse the getter antipattern or you abuse inheritance. It might be time for interface separation, composition, for use of mementos or commands. Something that reduces number of entry point in interface of single class. – Swift - Friday Pie Sep 02 '23 at 21:06

2 Answers2

5

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.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
1

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?.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96