1

class Frame<P> represents an image with pixels of type P. The algorithm that iterates through its pixels is non-trivial due to several flexibilities in the underlying data buffer format. Therefore I would like to avoid duplicating code in Frame<P>::iterate.

template<typename P>
class Frame {
    std::vector<P> buf;
public:
    template<typename F>
    void iterate(F f) const {
        // iterate in a way that is performant for this buffer
        // but here i use a simple iteration for demonstration
        for(const P& p : buf){
            for(int i=0; i<buf.size(); i++){
                f(buf.data()[p]);
            }
        }
    }
}

This allows (on a const Frame<P>& frame) for:

  • frame.iterate([](const P& p){ /* ... */ });

But I would like to also support (on a non-const Frame<P>& frame):

  • frame.iterate([](P& p){ /* ... */ });
  • std::move(frame).iterate([](P&& p){ /* ... */ });

Is there a simple way to do this without duplicating the code in iterate?

Museful
  • 6,711
  • 5
  • 42
  • 68
  • Possible duplicate of [How do I remove code duplication between similar ref-qualified member functions?](https://stackoverflow.com/questions/44644310/how-do-i-remove-code-duplication-between-similar-ref-qualified-member-functions) – Justin May 21 '19 at 22:07
  • It's a bit more than that, though. You'd also need to get a `std::move_iterator` from the `buf` member in the `&&` qualified function, or else you'd need some kind of `forward_like(p)` function for `for (auto&& p : buf) { f(forward_like(p)); }` to actually move the `p` element. – Justin May 21 '19 at 22:15
  • @Justin I've edited the implementation (while keeping it minimal) to reflect that it is actually iterating with pointer offsets. Do you think it can be done without duplication for my edited implementation? I didn't even know that member functions can be "ref-qualified". – Museful May 21 '19 at 22:35
  • What does your new implementation even mean? The `P` type is being used to index into the buffer itself, and the `i` is completely unused? Did you mean to remove the outer loop entirely? – Justin May 21 '19 at 23:48

1 Answers1

1

This is related to How do I remove code duplication between similar ref-qualified member functions?, but there's a bit more work needed. In short, we want to forward ref-qualified versions of iterate on to a static member function, which can appropriately forward on the individual elements of buf. Here's one way to do it:

template <typename P>
class Frame {
    std::vector<P> buf;

    template <typename F, typename Iter>
    static void iterate_impl(F&& f, Iter first, Iter last) {
        while (first != last) {
            f(*first++);
        }
    }

public:
    template <typename F>
    void iterate(F f) const& {
        iterate_impl(f, buf.begin(), buf.end());
    }

    template <typename F>
    void iterate(F f) & {
        iterate_impl(f, buf.begin(), buf.end());
    }

    template <typename F>
    void iterate(F f) && {
        iterate_impl(f,
            std::make_move_iterator(buf.begin()),
            std::make_move_iterator(buf.end()));
    }

    template <typename F>
    void iterate(F f) const&& {
        iterate_impl(f,
            std::make_move_iterator(buf.begin()),
            std::make_move_iterator(buf.end()));
    }
};

Another way to approach it is to have a forward_like function:

template <typename T, typename U>
struct copy_cv_ref {
private:
    using u_type = std::remove_cv_t<std::remove_reference_t<U>>;
    using t_type_with_cv = std::remove_reference_t<T>;

    template <bool condition, template <typename> class Q, typename V>
    using apply_qualifier_if = std::conditional_t<condition,
        Q<V>,
        V
    >;

    static constexpr bool is_lvalue = std::is_lvalue_reference<T>::value;
    static constexpr bool is_rvalue = std::is_rvalue_reference<T>::value;
    static constexpr bool is_const = std::is_const<t_type_with_cv>::value;
    static constexpr bool is_volatile = std::is_volatile<t_type_with_cv>::value;

public:
    using type =
        apply_qualifier_if<is_lvalue, std::add_lvalue_reference_t, 
        apply_qualifier_if<is_rvalue, std::add_rvalue_reference_t,
        apply_qualifier_if<is_volatile, std::add_volatile_t,
        apply_qualifier_if<is_const, std::add_const_t, u_type
    >>>>;
};

template <typename T, typename U>
using copy_cvref_t = typename copy_cv_ref<T, U>::type;

template <typename Like, typename U>
constexpr decltype(auto) forward_like(U&& it) {
    return static_cast<copy_cvref_t<Like&&, U&&>>(std::forward<U>(it));
}

Which you can then use in the implementation of iterate_impl:

template <typename P>
class Frame {
    std::vector<P> buf;

    template <typename Self, typename F>
    static void iterate_impl(Self&& self, F&& f) {
        for (int i = 0; i < self.buf.size(); ++i) {
            f(forward_like<Self>(self.buf[i]));
        }
    }

public:
    template <typename F>
    void iterate(F f) const& {
        iterate_impl(*this, f);
    }

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

    template <typename F>
    void iterate(F f) && {
        iterate_impl(std::move(*this), f);
    }

    template <typename F>
    void iterate(F f) const&& {
        iterate_impl(std::move(*this), f);
    }
};
Justin
  • 24,288
  • 12
  • 92
  • 142