Search code examples
c++lambdac++14rvalue-referenceperfect-forwarding

Perfect forwarding to a lambda received as an argument


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?


Solution

  • 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);
        }
    };