2

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

Here is a try, it works but I don't like it much.

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

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

    std::optional<T> optional_pop() {
        if(empty()) return {};
        return std::move(pop());
    }

    struct iterator {
        std::optional<T> _op_val;
        my_stack *_p_stack;

        T operator*() {
            return std::move(_op_val.value());
        }

        iterator& operator++() {
            _op_val = _p_stack->optional_pop();
            return *this;
        }

        auto operator<=>(const iterator&) const = default;
    };

    iterator end() {
        return iterator{{}, this};
    }

    iterator begin() {
        return iterator{optional_pop(), this};
    }
};

int main() {
    my_stack<int> s;
    
    for(auto val : s) {
        std::cout << val << std::endl;
    }
    s.push(1);
    s.push(3);
    s.push(5);
    s.push(7);

    for(auto val : s) {
        std::cout << val << std::endl;
    }
}
macomphy
  • 413
  • 1
  • 9
  • a stack only allows to access the top element, thats what makes it a stack – 463035818_is_not_an_ai Jul 21 '22 at 06:49
  • @463035818_is_not_a_number The elements in a range can be handled one by one, the elements in a stack can be handled one by one, too. So, it should be a range. – macomphy Jul 21 '22 at 06:53
  • 2
    The only way to read all the values in the stack would be to remove them one by one, thus empying the stack. Doesn't sound very range-like to me. – super Jul 21 '22 at 06:54
  • you can only "handle" the elements in the stack one by one if you pop them – 463035818_is_not_an_ai Jul 21 '22 at 06:54
  • @super Actually that might be useful. You could iterate over all the elements in a ranged for loop for example (of course only once) – Jakob Stark Jul 21 '22 at 06:55
  • @super think about an input iterator. It only support one pass algorithm like stack, too. https://en.cppreference.com/w/cpp/named_req/InputIterator – macomphy Jul 21 '22 at 06:57
  • 1
    note that `std::stack` is merely an adapter. Instead of `using base = std::stack;` you could directly use the underlying container (`std::deque` is the default). Then you need to expose `begin` and `end`, but then it isnt a stack anymore – 463035818_is_not_an_ai Jul 21 '22 at 06:58
  • maybe ```vector``` will do, it has built-in functions like ```back```, ```front```, ```push_back```, ```pop_back```, ```empty```,... you can use it as the core of your stack layer – Tan Jul 21 '22 at 06:59
  • 1
    @463035818_is_not_a_number I'd expose only the `const_iterator`s to allow inspecting the contents but at the same time to prevent *modifying* them. We'd get an `iterable_stack` then that way... – Aconcagua Jul 21 '22 at 07:13
  • 2
    I don't agree on the dublicate. A stack can per definition only be iterated (and emptied) once. There is however a `LegacyInputIterator` in the list of [iterator categories](https://en.cppreference.com/w/cpp/iterator#Iterator_categories) which explicitely allows this type of iteration. The only requirement for a `std::ranges::range` is, that the class provides means to get **any** iterator using `begin()` and `end()`. So by implementing those two functions accordingly, one should be able to make the class a single pass `std::ranges::range`. – Jakob Stark Jul 21 '22 at 07:15
  • 1
    @463035818_is_not_a_number The operations it provides (and expected cost) make the stack a stack. But a concrete stack might also be a range, allow indexed access from the top, ... It's just not specifically part of the stack interface. – Deduplicator Jul 21 '22 at 07:36
  • @Dedpuclicator hm, `std::stack` merely requires implementations to have same complexity as the underlying container. (I am not trying to defend my comment, so far I can agree with all comments arguing against my initial comment ;) – 463035818_is_not_an_ai Jul 21 '22 at 07:39

2 Answers2

2

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

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • Thanks, but it don't support my stack, seems we must have top(). – macomphy Jul 21 '22 at 08:39
  • @macomphy [Why does the standard separate `pop()` and `top()`](https://stackoverflow.com/a/12206358/11638718) – 康桓瑋 Jul 21 '22 at 08:48
  • @康桓瑋 that reasoning is mostly outdated now that we have move semantics, but it would be a breaking change to `std::stack` – Caleth Jul 21 '22 at 09:22
  • In fact, what I really mean is `file` in python and my stack is just to simulate it. It only have `readline()`, but it can have `for line in file`. – macomphy Jul 21 '22 at 09:31
0

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
}
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93