简体   繁体   中英

How to avoid code duplication between similar const and non-const member functions which pass class members to callbacks?

This is a specific case of this question where that answer doesn't directly work.

Consider :

struct hurg {};

class furg {
public:
    template <class F>
    void for_each_hurg(F&& f) const {
        for (auto& h : hurgs) {
            f(h);
        }
    }

    template <class F>
    void for_each_hurg(F&& f) {
        for (auto& h : hurgs) {
            f(h);
        }
    }
private:
    std::vector<hurg> hurgs;
};

Usage:

furg f;
const auto& cf = f;

f.for_each_hurg([](hurg& h) { });
cf.for_each_hurg([](const hurg& h) { });

The code for the const and non- const versions is identical, but only because auto& h infers const hurg& in the first case and hurg& in the second case.

In the spirit of the previously-linked-to Scott Meyers' solution , I came up with the following:

template <class F>
void for_each_hurg(F&& f) {
    const_cast<const furg&>(*this).for_each_hurg([&f](const hurg& h) {
        f(const_cast<hurg&>(h));
    });
}    

However, this seems like it could be more trouble than it's worth, especially if the types are long and if I can't use C++14's generic lambdas.

You can use a static member function template to forward *this to a generic function parameter:

template<typename Self, typename F>
static void for_each_hurg(Self& s, F&& f) {
    for (auto& h : s.hurgs) {
        f(h);
    }
}

template<typename F>
void for_each_hurg(F&& f) { for_each_hurg(*this, forward<F>(f))); }

template<typename F>
void for_each_hurg(F&& f) const { for_each_hurg(*this, forward<F>(f))); }

Since the advent of reference qualifiers for member functions, the general solution is to perfectly forward *this . This doesn't always matter, since you often don't want member functions to be called on rvalues anyway. I'll add this here since I think it is part of a more general solution.

Unfortunately, *this is always an lvalue, so you need additional manual care in the member function wrappers:

template<typename Self, typename F>
static void for_each_hurg(Self&& s, F&& f) {
    /* ... */
}

template<typename F>
void for_each_hurg(F&& f) && { for_each_hurg(move(*this), forward<F>(f))); }

template<typename F>
void for_each_hurg(F&& f) & { for_each_hurg(*this, forward<F>(f))); }

Which is unfortunately not symmetric :(


It is also possible to implement the above via a friend function template. This can have two benefits:

  • You can move the friend function template to namespace scope, which in the case of furg being a class template reduces the amount of function templates the compiler has to deal with (one per class template, instead of one per instantiation). This typically requires some boilerplate code and forward-declarations, though.
  • You can call the function with the same name either as a free function or as a member function, eg furg f; for_each_hurg(furg, [](hurg){}); furg.for_each_hurg([](hurg){}); furg f; for_each_hurg(furg, [](hurg){}); furg.for_each_hurg([](hurg){}); (When unqualified lookup finds a member function, it doesn't perform / ignores the results of ADL. Therefore, you'd have to put the friend function in namespace scope, in order to be able to refer to it via a qualified-id from within the non-static member function wrappers.)

Additionally, you'd have to protect that function template from being to greedy; either by putting it into some namespace detail or adding an enable-if clause. It's probably not worth the effort.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM