Home > Blockchain >  How can a stack be a range?
How can a stack be a range?

Time:07-21

Here is another question. But my question is not same as it. I want a stack can be handled like a one pass range, not to handle it like its' underlying container. The stack in my question only support pop and push , and once you read one element from the stack, it will be removed.

I have a stack that only support pop and push, here is an example. I can handle the elements in the stack one by one, just like a range. So I want it to be a range (support std::ranges::range). How can I do it elegantly?

#include <memory>
#include <iostream>
#include <stack>

template <typename T>
struct my_stack : private std::stack<T> {
    using base = std::stack<T>;
    using base::push;

    auto pop() {
        auto ret = std::move(base::top());
        base::pop();
        return std::move(ret);
    }

    bool empty() {
        return base::size() == 0;
    }
};

int main() {
    my_stack<int> s;
    s.push(1);
    s.push(3);
    s.push(5);
    s.push(7);

    while(!s.empty()) {
        std::cout << s.pop() << std::endl;
    }
}

CodePudding user response:

Implement your own stack_iterator (based on basic_istream_view​::​iterator), e.g.

#include <iterator>

template<class Stack>
class stack_iterator {
  Stack* s_;
public:
  using iterator_concept = std::input_iterator_tag;
  using value_type       = typename Stack::value_type;
  using difference_type  = ptrdiff_t;

  explicit stack_iterator(Stack& s) : s_(std::addressof(s)) { }
  stack_iterator(stack_iterator&&) = default;
  stack_iterator& operator=(stack_iterator&&) = default;
  stack_iterator(const stack_iterator&) = delete;
  stack_iterator& operator=(const stack_iterator&) = delete;

  decltype(auto) operator*() const { return s_->top(); }
  stack_iterator& operator  () { s_->pop(); return *this; }
  void operator  (int) {   *this; }
  bool operator==(std::default_sentinel_t) const {
    return s_->empty();
  }
};

Then you can use it like

std::stack<int> s;
std::ranges::copy(
  std::ranges::subrange(stack_iterator(s), std::default_sentinel),
  std::ostream_iterator<int>(std::cout , " "));

Demo

CodePudding user response:

Yes, you can. You will have to implement an input iterator for your type:

#include <stack>
#include <iterator>
#include <vector>
#include <algorithm>

template<class T> struct InputIterator;

template<class T> struct Stack : std::stack<T, std::vector<T>> {
  [[nodiscard]] InputIterator<T> begin() noexcept;
  [[nodiscard]] auto end() const noexcept { return nullptr; }
};

template<class T> struct InputIterator {
private:
  Stack<T> *data_;

public:
  explicit InputIterator(Stack<T> *stack) : data_{stack} {}

  InputIterator &operator  () noexcept {
    data_->pop();
    return *this;
  }

  [[nodiscard]] auto operator  (int) noexcept {
    struct Proxy {
      T i;
      [[nodiscard]] auto& operator*() const noexcept {
        return i;
      }
    };

    Proxy res{std::move(**this)};
      *this;
    return res;
  }

  [[nodiscard]] T &operator*() const noexcept { return data_->top(); }
  [[nodiscard]] Stack<T> const *operator->() const noexcept { return data_; }

  [[nodiscard]] bool operator==(InputIterator const &other) const noexcept {
    return data_ == other.data_;
  }

  [[nodiscard]] bool operator==(std::nullptr_t) const noexcept {
    return data_->empty();
  }
};

template<class T> InputIterator<T> Stack<T>::begin() noexcept {
  return InputIterator{this};
}

namespace std {
template<class T> struct iterator_traits<InputIterator < T>> {
  using difference_type = ptrdiff_t;
  using value_type = T;
  using pointer = T *;
  using reference = T &;
  using iterator_category = std::input_iterator_tag;
};
}

template<class T> auto begin(std::stack<T> &s) {
  return InputIterator<T>{&s};
}

int main() {
  Stack<int> s;
  s.push(1);
  s.push(2);
  s.push(3);

  std::vector<int> vec;
  std::ranges::copy(s, std::back_inserter(vec));
  // v -> 3, 2, 1
}
  •  Tags:  
  • c
  • Related