-
Notifications
You must be signed in to change notification settings - Fork 82
Session: Functional programming with C
Something you might know from variadic macros. Let us use define classes with zero or more template parameters or functions with zero or more arguments.
- http://en.cppreference.com/w/cpp/language/parameter_pack
- https://www.youtube.com/watch?v=VXi0AOQ0PF0&index=4&list=PLs3KjaCtOwSZ2tbuV1hx8Xz-rFZTan2J1&t=1161s
- http://eli.thegreenplace.net/2014/variadic-templates-in-c/
So let's start with implementing a generic zip iterator, which allows us to zip multiple iterators into one and to simultaneously iterate over a set of containers in one iteration loop.
Template code (click to expand)
```c++ #include #include #include #include
using namespace std;
template <typename first_type, typename ...remaining_type> class Zip_iterator { public: Zip_iterator() = delete;
Zip_iterator(first_type && first, remaining_type &&... remaining) : _data(std::make_tuple(first, remaining...)) {}
std::tuple< first_type, remaining_types...> _data; };
int main() { vector v{0,1,2,3,4,5}; string s{'a','b','c','d','e','f'};
Zip_iterator<vector<int>::iterator, string::iterator> zipper{v.begin(), s.end()};
return 0;
}
</p></detail>
In this code example we defined a class **Zip_iterator**, which can take 1 ore more iterator types.
The _..._ notation denotes the parameter pack.
The STL already gives us a data structure, which can hold 0 or more elements of different type, the [tuple](http://en.cppreference.com/w/cpp/utility/tuple) class.
However, the creation of the zipper is quite tedious, as we have to specify all template arguments, this zipper should be apply for. So we implement a utility function, called _zip_ similar to make_tuple, to not worry about the types that are stored in the zipper.
<details><summary>Template code (click to expand)</summary><p>
```c++
...
template <typename first_type, typename ...remaining_types>
inline auto
zip(first_type && first, remaining_types && ...remaining)
{
return Zip_iterator<first_type, remaining_types...>{first, std::forward<remaining_types>(remaining)...};
}
int main()
{
vector<int> v{0,1,2,3,4,5};
string s{'a','b','c','d','e','f'};
auto zipper = zip(v.begin(), s.end());
return 0;
}
Ok, now we have a generic zipper that can hold one or more iterators. Now let's start implementing the iteration. We will start with the ++operator.
Template code (click to expand)
```c++ ...
namespace impl { template <typename tuple_type, size_t ...index> inline void increment(tuple_type & data, std::index_sequence<Is...> const & /idx/) { (void) std::initializer_list{(++std::get(tuple), 0)...}; } }
template <typename ...iter_types> inline Zip_iterator<iter_types...>& operator++(Zip_iterator<iter_types...> & me /pre-increment/) { impl::increment(me._data, std::make_index_sequence<sizeof...(iter_types)>()); return me; } ... int main() { vector v{0,1,2,3,4,5}; string s{'a','b','c','d','e','f'};
auto zipper = zip(v.begin(), s.end());
return 0;
}
</p></detail>
When we call `++` we somehow need to access all the elements in the tuple and increment the contained iterator.
We could loop over the tuple, but there is a more efficient way to do it utilising pack expansion.
With `[std::make_index_sequence<SIZE>()](http://en.cppreference.com/w/cpp/utility/integer_sequence)`, we can get all the indices that are available for the tuple using the `sizeof...()` operator.
We than can expand all elements of the tuple via: `std::get<index>(tuple)...`.
Now we also want to apply a function to all expanded elements. This can be achieved in C++14 with the initializer_list trick. Here we kind of create an initialiser list of integers and create it with an comma-separated expression. C++ will evaluate all expressions and return the last one. This means in our example, that c++ first calls ++ on the iterator at the corresponding expanded index of the tuple and then returns 0, to fill the initialiser list. Thus we can apply a function to all elements in the tuple with expansion semantics.
# Lambda functions:
### Recommended Reading/Viewing
* http://en.cppreference.com/w/cpp/language/lambda
* http://www.cprogramming.com/c++11/c++11-lambda-closures.html
Now we are going to implement multiple operators on the zip iterator. Since we want to reuse the initialiser trick, we will write a utility function called `apply_to`, which takes an arbitrary function and applies the elements in the tuple to it. We then can rewrite `++operator` to also submit a lambda function which implements the ++operation on the
<details><summary>Template code (click to expand)</summary><p>
```c++
...
namespace impl
{
template <typename tuple_type, typename func_type,
size_t ...index>
inline void
apply_each(tuple_type && tuple,
func_type && func,
std::index_sequence<index...> const &/*idx*/)
{
(void) std::initializer_list<int>{(func(std::get<index>(tuple)), 0)...};
}
}
template <typename ...iter_types>
inline Zip_iterator<iter_types...>&
operator++(Zip_iterator<iter_types...> & me /*pre-increment*/)
{
impl::apply_each(me._data, [](auto & it){ ++it; }, std::make_index_sequence<sizeof...(iter_types)>());
return me;
}
template <typename ...iter_types>
inline Zip_iterator<iter_types...>&
operator--(Zip_iterator<iter_types...> & me /*pre-decrement*/)
{
impl::apply_each(me._data, [](auto & it){ --it; }, std::make_index_sequence<sizeof...(iter_types)>());
return me;
}
...
int main()
{
vector<int> v{0,1,2,3,4,5};
string s{'a','b','c','d','e','f'};
auto zipper = zip(v.begin(), s.end());
++zipper;
--zipper;
return 0;
}