Generating functors with iterator behavior

前端 未结 4 1730
温柔的废话
温柔的废话 2021-02-04 13:53

I have a question, which very likely has been asked like this before, because I think what I want is something that a considerable amount of people would want. However I could n

相关标签:
4条回答
  • 2021-02-04 14:14

    I wonder if this could be called tabulation. If so, what do you thing about the following interface?

    #include <iostream>
    #include <vector>
    #include <list>
    
    #include "tabulate.hpp"
    
    template<typename T>
    void show(const T& data) {
      for(const auto & x: data) std::cout << x << " ";
      std::cout << std::endl;
    }
    
    int main() {
      auto fun = [](double x) { return 2.0 * x; };
    
      std::vector<double> x  {1, 2, 3, 4, 5};
    
      std::cout << "using range-for" << std::endl;
      for(const auto & fx : tabulate(fun, x.begin(), x.end())) {
        std::cout << fx << std::endl;
      }
    
      std::cout << "initializing a vector" << std::endl;
      auto init = tabulate(fun, x.begin(), x.end());
      std::vector<double> values(init.begin(), init.end());
      show(values);
    
      std::cout << "automatic construction of vector" << std::endl;
      auto in_vector = make_tabulation<std::vector<double>>(fun, x);
      show(in_vector);
    
      std::cout << "automatic construction of list" << std::endl;
      auto in_list   = make_tabulation<std::list<double>>(fun, x);
      show(in_list);
    }
    

    Which is implemented by the following header:

    #pragma once
    #include <iterator>
    
    template<typename Fun,
             typename InputIt,
             typename T = typename std::iterator_traits<InputIt>::value_type
             >
    class tabulate_iterator
    
        : public std::iterator<std::input_iterator_tag, T> {
    
     public:
    
      tabulate_iterator()
          : m_is_valid(false) { }
    
      tabulate_iterator(Fun& fun, InputIt beg, InputIt end)
          : m_fun(&fun),
            m_beg(beg),
            m_end(end),
            m_is_valid(beg != end) {
        this->read();
      }
    
      const T& operator*() const {
        return m_current;
      }
    
      const T* operator->() const {
        return &(operator*());
      }
    
      tabulate_iterator& operator++() {
        this->read();
        return *this;
      }
    
      tabulate_iterator operator++(int) {
        auto tmp = *this;
        this->read();
        return tmp;
      }
    
      bool equals(const tabulate_iterator& other) const {
        return ((m_is_valid == other.m_is_valid) and
                (not m_is_valid));
      }
    
      bool operator==(const tabulate_iterator& other) const {
        return this->equals(other);
      }
    
      bool operator!=(const tabulate_iterator& other) const {
        return not this->equals(other);
      }
    
     private:
    
      void read() {
        if(m_is_valid and m_beg != m_end) {
          m_current = (*m_fun)(*m_beg++);
        } else {
          m_is_valid = false;
        }
      }
    
      T       m_current;
      Fun*    m_fun;
      InputIt m_beg;
      InputIt m_end;
      bool    m_is_valid;
    
    };
    
    template<typename Fun,
             typename InputIt,
             typename T = typename std::iterator_traits<InputIt>::value_type
             >
    class tabulate_range {
    
     public:
    
      tabulate_iterator<Fun, InputIt, T> begin() const {
        return m_it;
      }
    
      tabulate_iterator<Fun, InputIt, T> end() const {
        return m_it_end;
      }
    
     private:
    
      template<typename Fun_, typename InputIt_, typename T_>
      friend tabulate_range<Fun_, InputIt_, T_> tabulate(Fun_, InputIt_, InputIt_);
    
      tabulate_range(Fun fun, InputIt beg, InputIt end)
          : m_it(fun, beg, end),
            m_it_end() { }
    
      tabulate_iterator<Fun, InputIt, T> m_it;
      tabulate_iterator<Fun, InputIt, T> m_it_end;
    };
    
    template<typename Fun,
             typename InputIt,
             typename T = typename std::iterator_traits<InputIt>::value_type
             >
    tabulate_range<Fun, InputIt, T> tabulate(Fun fun, InputIt beg, InputIt end) {
      return tabulate_range<Fun, InputIt, T>(fun, beg, end);
    }
    
    template<typename OutContainer, typename Fun, typename InContainer>
    OutContainer make_tabulation(Fun fun, const InContainer& x) {
      auto init = tabulate(fun, x.begin(), x.end());
      return OutContainer(init.begin(), init.end());
    }
    

    Some caveats: I just cracked this code in the spur of the moment, so bugs are likely; take this as a proof of concept.

    Compilation (GCC 4.8.2/Linux; ICC 14.0.2 20140120/Linux):

    {CXX} tabulate.cpp -std=c++11 -Wall -Wextra -Werror
    

    Output:

    $ ./a.out
    using range-for
    2
    4
    6
    8
    10
    initializing a vector
    2 4 6 8 10
    automatic construction of vector
    2 4 6 8 10
    automatic construction of list
    2 4 6 8 10
    
    0 讨论(0)
  • 2021-02-04 14:21

    The Boost.Range way to solve this problem is to use the transform iterator adaptor:

    auto rng = boost::irange(1, 10)
        | boost::adaptors::transformed([](int i) { return i * i; });
    std::vector<int> v{rng.begin(), rng.end()};
    

    Note how this separates the concerns of the transformation from the start/stop/step (optional) parameters of the input range.

    0 讨论(0)
  • 2021-02-04 14:21

    The C++ library already offers some algorithms that implement most of the functionality you're trying to do on your own. I think it's better for you to adapt your template so that it would work seamlessly with the C++ library, instead.

    I'm thinking specifically of std::generate().

    So, you take what you're planning to do in your step #1, but replace step #2 with implementing an operator() that returns the next value in the sequence.

    Then, you can let std::generate() take care of populating an actual sequence with your values.

    0 讨论(0)
  • 2021-02-04 14:25

    Sure, we can just write our own iterator:

    template <typename F, typename Value>
    class func_iterator
    : std::iterator<
        std::random_access_iterator_tag,
        typename std::result_of<F(Value)>::type,
        Value,
        typename std::result_of<F(Value)>::type,
        typename std::result_of<F(Value)>::type>
    { .. };
    

    This iterator needs three things: a function (F f), the current value and the step (Value value, step). Dereferencing will calculate the value of the function each time:

    using T = typename std::result_of<F(Value)>::type;
    T operator*() { return f(value); }
    

    Selected iterating functions (omitting postfix since they look the same):

    func_iterator& operator++() {
        value += step;
        return *this;
    }
    
    func_iterator& operator--() {
        value -= step;
        return *this;
    }
    
    func_iterator operator+(Value amt) {
        return func_iterator{f, value + amt * step, step};
    }
    

    Difference between iterators (for std::distance) and equality:

    Value operator-(const func_iterator& rhs) {
        assert(step == rhs.step);
        return (value - rhs.value) / step;
    }
    
    bool operator==(const func_iterator& rhs) {
        return value == rhs.value && step == rhs.step;
    }
    

    And finally a function to make an iterator for us:

    template <typename F, typename Value>
    func_iterator<F, Value> make_func_iterator(F f, Value v, Value s = 1) {
        return func_iterator<F, Value>{f, v, s};
    }
    

    Putting that together, I can do something like:

    auto sq_it = make_func_iterator([](int x){return x*x;}, 1);
    std::vector<int> squares{sq_it, sq_it + 10}; // v now holds {1, 4, 9, 16, ..., 100}
    

    Or just:

    // get a bunch of even numbers, complicatedly:
    auto ident = make_func_iterator([](int x){return x;}, 2, 2);
    std::vector<int> evens{ident, ident+200}; // holds {2, 4, ..., 400}
    
    0 讨论(0)
提交回复
热议问题