Идеальная переадресация на лямбду, полученную в качестве аргумента

class Frame<P> представляет изображение с пикселями типа P. Алгоритм, который перебирает его пиксели, нетривиален из-за нескольких гибких возможностей в базовом формате буфера данных. Поэтому я хотел бы избежать дублирования кода в 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]);
            }
        }
    }
}

Это позволяет (на const Frame<P>& frame) для:

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

Но я хотел бы также поддержать (на неконстантном Frame<P>& frame):

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

Есть ли простой способ сделать это без дублирования кода в iterate?


person Museful    schedule 21.05.2019    source источник
comment
Это немного больше, чем это, хотя. Вам также нужно будет получить std::move_iterator от члена buf в квалифицированной функции &&, иначе вам понадобится какая-то функция forward_like<Self>(p) для for (auto&& p : buf) { f(forward_like<Self>(p)); }, чтобы фактически переместить элемент p.   -  person Justin    schedule 22.05.2019
comment
@Justin Я отредактировал реализацию (сохранив ее минимальной), чтобы отразить, что она фактически выполняет итерацию со смещением указателя. Как вы думаете, это можно сделать без дублирования для моей отредактированной реализации? Я даже не знал, что функции-члены могут быть квалифицированы по ссылке.   -  person Museful    schedule 22.05.2019
comment
Что вообще означает ваша новая реализация? Тип P используется для индексации самого буфера, а i совершенно не используется? Вы имели в виду полностью удалить внешний цикл?   -  person Justin    schedule 22.05.2019


Ответы (1)


Это связано с Как удалить дублирование кода между похожими функциями-членами с квалификацией ref?, но требуется немного больше работы . Короче говоря, мы хотим пересылать версии iterate с указанием ссылки в статическую функцию-член, которая может надлежащим образом пересылать отдельные элементы buf. Вот один из способов сделать это:

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

Другой способ приблизиться к этому — использовать функцию forward_like:

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

Который затем можно использовать в реализации 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);
    }
};
person Justin    schedule 22.05.2019