Enabling range-based for loops for custom types

As we have seen in the preceding recipe, the range-based for loops, known as for each in other programming languages, allows you to iterate over the elements of a range, providing a simplified syntax over the standard for loops and making the code more readable in many situations. However, range-based for loops do not work out of the box with any type representing a range, but require the presence of a begin() and end() function (for non-array types) either as a member or free function(非成员函数). In this recipe, we will see how to enable a custom type to be used in range-based for loops.

Getting ready

It is recommended that you read the recipe Using range-based for loops to iterate on a range before continuing with this one if you need to understand how range-based for loops work and what is the code the compiler generates for such a loop.

To show how we can enable range-based for loops for custom types representing sequences, we will use the following implementation of a simple array:

template <typename T, size_t const Size> 
class dummy_array { 
	T data[Size] = {}; 

public: 
	T const & GetAt(size_t const index) const { 
		if (index < Size) return data[index]; 
		throw std::out_of_range("index out of range"); 
	} 

	void SetAt(size_t const index, T const & value) { 
		if (index < Size) data[index] = value; 
		else throw std::out_of_range("index out of range"); 
	} 

	size_t GetSize() const { return Size; } 
};

The purpose of this recipe is to enable writing code like the following:

dummy_array<int, 3> arr; 
arr.SetAt(0, 1); 
arr.SetAt(1, 2); 
arr.SetAt(2, 3); 

for(auto&& e : arr) {  
  std::cout << e << std::endl; 
}

How to do it…

To enable a custom type to be used in range-based for loops, you need to do the following:

  • Create mutable and constant iterators for the type that must implement the following operators:
  1. operator++ for incrementing the iterator.
  2. operator* for dereferencing the iterator and accessing the actual element pointed by the iterator.
  3. operator!= for comparing with another iterator for inequality.
  • Provide free begin() and end() functions for the type.

Given the earlier example of a simple range, we need to provide the following:

  1. The following minimal implementation of an iterator class:

    template <typename T, typename C, size_t const Size> 
    class dummy_array_iterator_type { 
    public:
    	dummy_array_iterator_type(C& collection, size_t const index) : 
       		index(index), collection(collection) {} 
    
    	bool operator!= (dummy_array_iterator_type const & other) const { 
    	  return index != other.index; 
    	} 
    
    	T const & operator* () const { 
    	  return collection.GetAt(index); 
    	} 
    
    	dummy_array_iterator_type const & operator++ () { 
    	  ++index; 
    	  return *this; 
    	} 
    
    private: 
      size_t   index; 
      C&       collection; 
    };
    
  2. Alias templates for mutable and constant iterators:

    template <typename T, size_t const Size> 
    using dummy_array_iterator =  
      dummy_array_iterator_type<T, dummy_array<T, Size>, Size>; 
    
    template <typename T, size_t const Size> 
    using dummy_array_const_iterator =  
      dummy_array_iterator_type<T, dummy_array<T, Size> const, Size>;
    
  3. Free begin() and end() functions that return the corresponding begin and end iterators, with overloads for both alias templates:

    template <typename T, size_t const Size> 
    inline dummy_array_iterator<T, Size> begin(dummy_array<T, Size>& collection) { 
      return dummy_array_iterator<T, Size>(collection, 0); 
    } 
    
    template <typename T, size_t const Size> 
    inline dummy_array_iterator<T, Size> end(dummy_array<T, Size>& collection) { 
      return dummy_array_iterator<T, Size>(collection, collection.GetSize()); 
    } 
    
    template <typename T, size_t const Size> 
    inline dummy_array_const_iterator<T, Size> begin(dummy_array<T, Size> const & collection) { 
      return dummy_array_const_iterator<T, Size>(collection, 0); 
    } 
    
    template <typename T, size_t const Size> 
    inline dummy_array_const_iterator<T, Size> end(dummy_array<T, Size> const & collection) { 
      return dummy_array_const_iterator<T, Size>(collection, collection.GetSize()); 
    }
    

How it works…

Having this implementation available, the range-based for loop shown earlier compiles and executes as expected. When performing argument dependent lookup, the compiler will identify the two begin() and end() functions that we wrote (that take a reference to a dummy_array) and therefore the code it generates becomes valid.

In the preceding example, we have defined one iterator class template and two alias templates, called dummy_array_iterator and dummy_array_const_iterator. The begin() and end() functions both have two overloads for these two types of iterators. This is necessary so that the container we have considered could be used in range-based for loops with both constant and non-constant instances:

template <typename T, const size_t Size> 
void print_dummy_array(dummy_array<T, Size> const & arr) { 
  for (auto && e : arr) { 
    std::cout << e << std::endl; 
  } 
}

A possible alternative to enable range-based for loops for the simple range class we considered for this recipe is to provide member begin() and end() functions. In general, that could make sense only if you own and can modify the source code. On the other hand, the solution shown in this recipe works in all cases and should be preferred to other alternatives.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值