etaler/Etaler

Iteration over tensor

Closed this issue · 3 comments

To match numpy's behavior, we should make the Tensor object iteratable. And iterates over the first dim by default.

Ex:

a = ones({4, 4});
for(auto s : a)
    a += 1; 

etc...

This turns out to be quite of a problem. Since views are generated on the fly; the C++ iterator architecture is fitting badly.

//Iterator
struct ETALER_EXPORT iterator
{
	iterator(Tensor& t) : t_(&t) {}
	Tensor operator*() { return t_->view({curr_}); } // Can't return ref here
	Tensor* operator->() {/*real? How am I going to return a pointer from a local variable*/}

	bool operator==(iterator rhs) { return curr_ == rhs.curr_ && t_ == rhs.t_; }
	bool operator!=(iterator rhs) { !(*this == rhs); }
	iterator& operator++() {curr_ += 1; return *this;}
       	iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
	Tensor* t_; // Using a pointer because Tensor is a incomplete type within the type itself
	intmax_t curr_ = 0;
};

This is the best I can do

template <typename T>
struct ETALER_EXPORT TensorIterator
{
	// Iterator properties
	using iterator_category = std::bidirectional_iterator_tag;
	using value_type = T;
	using raw_value_type = std::remove_const_t<value_type>; // extra
	using difference_type = intmax_t;
	using pointer = std::unique_ptr<raw_value_type>;
	using reference = T&;

	using ThisIterator = TensorIterator<T>;
	TensorIterator() = default;
	TensorIterator(reference t, intmax_t offset = 0) : t_(&t), offset_(offset)
	{static_assert(std::is_same_v<raw_value_type, Tensor>); }
	value_type operator*() { return t_->view({offset_}); }
	// Unfortunatelly returning a pointer is not doable
	pointer operator->() { return std::make_unique<raw_value_type>(*(*this)); }
	bool operator==(ThisIterator rhs) const { return offset_ == rhs.offset_ && t_ == rhs.t_; }
	bool operator!=(ThisIterator rhs) const { return !(*this == rhs); }
	ThisIterator& operator++() {offset_ += 1; return *this;}
	ThisIterator operator++(int) {ThisIterator retval = *this; ++(*this); return retval;}
	ThisIterator& operator--() {offset_ -= 1; return *this;}
	ThisIterator operator--(int) {ThisIterator retval = *this; --(*this); return retval;}
	value_type* t_ = nullptr; // Using a pointer because Tensor is a incomplete type here
	intmax_t offset_ = 0;
};

It works 90% of the time and have few caveats

  • operator* should return a reference
  • operator-> should return a pointer
  • swap() (not in the shown code` should accept references, not values

#121 implements a bidirectional iterator. It should be enough for most use cases.
Feel free to reopen the issue if we need a random access iterator