Use std::vector for std::array initialization - c++

Suppose I have a std::vector of a size known at compile time, and I want to turn that into an std::array. How would I do that? Is there a standard function to do this?
The best solution I have so far is this:
template<class T, std::size_t N, class Indexable, std::size_t... Indices>
std::array<T, N> to_array_1(const Indexable& indexable, std::index_sequence<Indices...>) {
return {{ indexable[Indices]... }};
}
template<class T, std::size_t N, class Indexable>
std::array<T, N> to_array(const Indexable& indexable) {
return to_array_1<T, N>(indexable, std::make_index_sequence<N>());
}
std::array<Foo, 123> initFoos {
std::vector<Foo> lst;
for (unsigned i = 0; i < 123; ++i)
lst.push_back(getNextFoo(lst));
return to_array<Foo, 123>(lst); // passing lst.begin() works, too
}
The application is similar to Populate std::array with non-default-constructible type (no variadic templates): I too have a type which is not default-constructible, so I need to compute the actual values by the time the array gets initialized. However contrary to that question, for me the values are not merely a function of the index, but also of the preceding values. I can build my values much more easily using a loop than a series of function calls. So I construct the elements in a loop and place them in a vector, then I want to use the final state of that vector to initialize the array.
The above seems to compile and work fine, but perhaps there are ways to improve it.
Perhaps I could make clever use of some standard library functionality that I wasn't aware of.
Perhaps I can avoid the helper function somehow.
Perhaps I can somehow formulate this in such a way that it works with move semantics for the elements instead of the copy semantics employed above.
Perhaps I can avoid the random access using operator[], and instead use forward iterator semantics only so it would work for std::set or std::forward_list as input, too.
Perhaps I should stop using std::vector and instead express my goal using std::array<std::optional<T>, N> instead, using C++17 or some equivalent implementation.
Related questions:
Copy std::vector into std::array which doesn't assume a type without default constructor, so copying after default initialization is feasible there but not for me.
Populate std::array with non-default-constructible type (no variadic templates) which computes each element from its index independently, so it tries to avoid an intermediate container. Answers use variadic templates even though the title requests avoiding them.

I would propose:
template<typename T, typename Iter, std::size_t... Is>
constexpr auto to_array(Iter& iter, std::index_sequence<Is...>)
-> std::array<T, sizeof...(Is)> {
return {{ ((void)Is, *iter++)... }};
}
template<std::size_t N, typename Iter,
typename T = typename std::iterator_traits<Iter>::value_type>
constexpr auto to_array(Iter iter)
-> std::array<T, N> {
return to_array<T>(iter, std::make_index_sequence<N>{});
}
This deduces the element type from the iterator and leaves copy-vs-move semantics up to the caller – if the caller wants to move, they can opt-in via std::move_iterator or the like:
auto initFoos() {
constexpr unsigned n{123};
std::vector<Foo> lst;
for (unsigned i{}; i != n; ++i) {
lst.push_back(getNextFoo(lst));
}
// copy-init array elements
return to_array<n>(lst.cbegin());
// move-init array elements
return to_array<n>(std::make_move_iterator(lst.begin()));
}
Online Demo
EDIT: If one wants to override the deduced element type, as indicated in the comments, then I propose:
template<typename T, typename Iter, std::size_t... Is>
constexpr auto to_array(Iter& iter, std::index_sequence<Is...>)
-> std::array<T, sizeof...(Is)> {
return {{ ((void)Is, T(*iter++))... }};
}
template<std::size_t N, typename U = void, typename Iter,
typename V = typename std::iterator_traits<Iter>::value_type,
typename T = std::conditional_t<std::is_same<U, void>{}, V, U>>
constexpr auto to_array(Iter iter)
-> std::array<T, N> {
return to_array<T>(iter, std::make_index_sequence<N>{});
}
This leaves the element type optional but makes it the second parameter rather than the first, so usage would look like to_array<N, Bar>(lst.begin()) rather than to_array<Bar, N>(lst.begin()).

Related

C++11 enable_if on return type of subscript operator of template parameter

I'm working with vector classes containing either integer or floating point types. I'd like to choose one or the other function template accordingly, but the only way to deduce the type is through the subscript operator [].
Is there a way to use enable_if<is_integral< ... on the return type of the [] operator of the template parameter?
Something like:
template<class V, typename enable_if<is_integral<V::operator[]>::value, int>::type = 0>
int MyFunc(V vec)
{ return vec[0]; }
template<class V, typename enable_if<!is_integral<V::operator[]>::value, int>::type = 0>
int MyFunc(V vec)
{ return ceil(vec[0]); }
Some general suggestion
I would avoid using namespace std which your code implies (almost) that you use.
You can shorten and make more readable std::enable_if<T>::type and std::is_integral<T>::value by writing them as std::enable_if_t<T> and std::is_integral_v<T> respectively (search for "Helper types" here, for instance).
The answer to your question
You need to apply operator[] to something
What you really want to ask is if the items in the object of type V can be accessed via [], so you want to check whether something like V{}[0] is valid, so you would write something like this (as to why std::decay_t is needed, see note (¹)):
template<class V, typename std::enable_if_t<std::is_integral_v<std::decay_t<decltype(V{}[0])>>, int> = false>
But V might be not default constructible
If you want to support types V without assuming that they are default constructible you can use std::declval to "pretend" you create a value of that type (here std::decay_t is needed for the same reason as above):
template<class V, typename std::enable_if_t<std::is_integral_v<std::decay_t<decltype(std::declval<V>()[0])>>, int> = false>
And operator[] could return some proxy to the actual entities in V
In general operator[] doesn't return an object of the type which is "conceptually" the type stored in the container; for instance, for std::vector<bool>, operator[] doesn't return a bool&, but a proxy type²; in those cases, you really want to rely on the value_type type alias stored in the container:
template<class V, typename std::enable_if_t<std::is_integral_v<typename V::value_type>, int> = false>
Something might be not value_type-equipped but only provide .begin()/.end()
As a last improvement, we can also remove the assumption that the V has a value_type member alias (which is true for STL container), and simply require that it has a begin member function (STL container have it too), and make use of ranges::iter_value_t³:
template<class V, typename std::enable_if_t<std::is_integral_v<ranges::iter_value_t<decltype(std::declval<V>().begin())>>, int> = 0>
Notice that
ranges::iter_value_t<decltype(std::declval<V>().begin())>
is in general not the same same thing as
std::decay_t<decltype(*std::declval<V>().begin())>
a case of when they are different being, again, std::vector<bool>.
¹ std::vector<int>::operator[], for instance, returns int&, not int, and int& is not integral:
static_assert(std::is_integral_v<int&>); // fails
² For instance:
// this passes
static_assert(std::is_same_v<int, std::decay_t<decltype(std::declval<std::vector<int>>()[0])>>);
// this doesn't!
static_assert(std::is_same_v<bool, std::decay_t<decltype(std::declval<std::vector<bool>>()[0])>>);
// with my compiler, this one does pass
static_assert(std::is_same_v<std::_Bit_reference, std::decay_t<decltype(std::declval<std::vector<bool>>()[0])>>);
³ ranges::iter_value_t is from the header #include <range/v3/iterator/access.hpp> of the Range-v3 library. The documentation of that library is close to crap, so you can refer the doc of the analagous C++20 functionality on cppreference.
Why are you not using std::vector::value_type?
template<class V, typename enable_if<is_integral<typename V::value_type>::value, int>::type = 0>
int MyFunc(V vec)
{ return vec[0]; }
template<class V, typename enable_if<!is_integral<typename V::value_type>::value, int>::type = 0>
int MyFunc(V vec)
{ return ceil(vec[0]); }

Can type arguments be made deduceable for function templates using std container?

I found this implementation of a few common features of functional programming, e.g. map / reduce:
(I'm aware stuff like that is aparently coming or partially present in new C++ versions)
github link
A part of the code:
template <typename T, typename U>
U foldLeft(const std::vector<T>& data,
const U& initialValue,
const std::function<U(U,T)>& foldFn) {
typedef typename std::vector<T>::const_iterator Iterator;
U accumulator = initialValue;
Iterator end = data.cend();
for (Iterator it = data.cbegin(); it != end; ++it) {
accumulator = foldFn(accumulator, *it);
}
return accumulator;
}
template <typename T, typename U>
std::vector<U> map(const std::vector<T>& data, const std::function<U(T)> mapper) {
std::vector<U> result;
foldLeft<T, std::vector<U>&>(data, result, [mapper] (std::vector<U>& res, T value) -> std::vector<U>& {
res.push_back(mapper(value));
return res;
});
return result;
}
Usage example:
std::vector<int> biggerInts = map<int,int>(test, [] (int num) { return num + 10; });
The type arguments T,U have to be fully qualified for this to compile, as shown in the example, with e.g. map< int,int >( ... ).
This implementation is for C++11, as mentioned on the linked-to page.
Is it possible with newer C++ versions (or even 11) now to make the use of this less verbose, i.e. making the types U,T deduce automatically?
I have googled for that and only found that there is apparently some improvement for class template, as opposed to function template, argument deduction in C++17.
But since I only ever used templates in a rather basic manner, I was wondering whether there is something in existence that I'm not aware of which could improve this implementation verboseness-wise.
You can rewrite map signature to be:
template <typename T, typename M, typename U = decltype(std::declval<M>()(T{}))>
std::vector<U> map(const std::vector<T>& data, const M mapper)
then T will be deduced as value_type of vector's items.
M is any callable object.
U is deduced as return type of M() functor when called for T{}.
Below
std::vector<int> biggerInts = map(test, [] (int num) { return num + 10; });
^^^^ empty template arguments list
works fine.
Live demo
More general templates make template argument deduction easier.
One principle: it is often a mistake to use a std::function as a templated function's parameter. std::function is a type erasure, for use when something needs to store some unknown invokable thing as a specific type. But templates already have the ability to handle any arbitrary invokable type. So if we just use a generic typename FuncT template parameter, it can be deduced for a raw pointer-to-function, a lambda, or another class with operator() directly.
We might as well also get more general and accept any input container instead of just vector, then determine T from it, if it's even directly needed.
So for C++11 I would rewrite these:
// C++20 is adding std::remove_cvref, but it's trivial to implement:
template <typename T>
using remove_cvref_t =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <typename Container, typename U, typename FuncT>
remove_cvref_t<U> foldLeft(
const Container& data,
U&& initialValue,
const FuncT& foldFn) {
remove_cvref_t<U> accumulator = std::forward<U>(initialValue);
for (const auto& elem : data) {
accumulator = foldFn(std::move(accumulator), elem);
}
return accumulator;
}
template <typename Container, typename FuncT>
auto map(const Container& data, const FuncT& mapper)
-> std::vector<remove_cvref_t<decltype(mapper(*std::begin(data)))>>
{
using T = remove_cvref_t<decltype(*std::begin(data))>;
using ResultT = std::vector<remove_cvref_t<decltype(mapper(std::declval<const T&>()))>>;
ResultT result;
foldLeft(data, std::ref(result), [&mapper] (ResultT &res, const T& value) -> ResultT& {
res.push_back(mapper(value));
return res;
});
return result;
}
See the working program on coliru.
There was one unfortunate thing about the old map: it potentially copied the result vector at every iteration. The = in accumulator = foldFn(accumulator, *it); is a self-assignment, which might do nothing, or might allocate new memory, copy contents, then free the old memory and update the container. So instead I've changed the U for foldLeft in this case to a std::reference_wrapper. The = in that case will still "rebind" the wrapper to the same object, but that will at least be quick.
In C++14 and later, you could do away with finding T within map by using a generic lambda: [&mapper] (std::vector<U>& res, const auto& value) ...

C++ Transform a std::tuple<A, A, A...> to a std::vector or std::deque

In the simple parser library I am writing, the results of multiple parsers is combined using std::tuple_cat. But when applying a parser that returns the same result multiple times, it becomes important to transform this tuple into a container like a vector or a deque.
How can this be done? How can any tuple of the kind std::tuple<A>, std::tuple<A, A>, std::tuple<A, A, A> etc be converted into a std::vector<A>?
I think this might be possible using typename ...As and sizeof ...(As), but I am not sure how to create a smaller tuple to call the function recursively. Or how to write an iterative solution that extracts elements from the tuple one by one. (as std::get<n>(tuple) is constructed at compile-time).
How to do this?
With the introduction of std::apply(), this is very straightforward:
template <class Tuple,
class T = std::decay_t<std::tuple_element_t<0, std::decay_t<Tuple>>>>
std::vector<T> to_vector(Tuple&& tuple)
{
return std::apply([](auto&&... elems){
return std::vector<T>{std::forward<decltype(elems)>(elems)...};
}, std::forward<Tuple>(tuple));
}
std::apply() is a C++17 function but is implementable in C++14 (see link for possible implementation). As an improvement, you could add either SFINAE or a static_assert that all the types in the Tuple are actually T.
As T.C. points out, this incurs an extra copy of every element, since std::initializer_list is backed by a const array. That's unfortunate. We win some on not having to do boundary checks on every element, but lose some on the copying. The copying ends up being too expensive, an alternative implementation would be:
template <class Tuple,
class T = std::decay_t<std::tuple_element_t<0, std::decay_t<Tuple>>>>
std::vector<T> to_vector(Tuple&& tuple)
{
return std::apply([](auto&&... elems) {
using expander = int[];
std::vector<T> result;
result.reserve(sizeof...(elems));
expander{(void(
result.push_back(std::forward<decltype(elems)>(elems))
), 0)...};
return result;
}, std::forward<Tuple>(tuple));
}
See this answer for an explanation of the expander trick. Note that I dropped the leading 0 since we know the pack is non-empty. With C++17, this becomes cleaner with a fold-expression:
return std::apply([](auto&&... elems) {
std::vector<T> result;
result.reserve(sizeof...(elems));
(result.push_back(std::forward<decltype(elems)>(elems)), ...);
return result;
}, std::forward<Tuple>(tuple));
Although still relatively not as nice as the initializer_list constructor. Unfortunate.
Here's one way to do it:
#include <tuple>
#include <algorithm>
#include <vector>
#include <iostream>
template<typename first_type, typename tuple_type, size_t ...index>
auto to_vector_helper(const tuple_type &t, std::index_sequence<index...>)
{
return std::vector<first_type>{
std::get<index>(t)...
};
}
template<typename first_type, typename ...others>
auto to_vector(const std::tuple<first_type, others...> &t)
{
typedef typename std::remove_reference<decltype(t)>::type tuple_type;
constexpr auto s =
std::tuple_size<tuple_type>::value;
return to_vector_helper<first_type, tuple_type>
(t, std::make_index_sequence<s>{});
}
int main()
{
std::tuple<int, int> t{2,3};
std::vector<int> v=to_vector(t);
std::cout << v[0] << ' ' << v[1] << ' ' << v.size() << std::endl;
return 0;
}
Although, this doesn't answer the question completely, This still might be suitable in some cases. Only when the number of elements in tuple is around 5, 6. (And you know the size).
tuple<int, int, int, int> a = make_tuple(1, 2, 3, 4);
auto [p, q, r, s] = a;
vector<int> arr(p, q, r, s); // Now, arr has the same elements as in tuple a
Note that, this is C++ 17 feature. More info here

Pushing and popping the first element of a std::tuple

I am writing a function in C++ with a variable number of arguments (and different types) in this way
template<typename ...Ts>
void myFunction(Ts ...args)
{
//create std::tuple to access and manipulate single elements of the pack
auto myTuple = std::make_tuple(args...);
//do stuff
return;
}
What i would like to do, but I don't know how, is to push and pop elements from the tuple, in particular the first element... something like
//remove the first element of the tuple thereby decreasing its size by one
myTuple.pop_front()
//add addThis as the first element of the tuple thereby increasing its size by one
myTuple.push_front(addThis)
Is this possible?
You may do something like
template <typename T, typename Tuple>
auto push_front(const T& t, const Tuple& tuple)
{
return std::tuple_cat(std::make_tuple(t), tuple);
}
template <typename Tuple, std::size_t ... Is>
auto pop_front_impl(const Tuple& tuple, std::index_sequence<Is...>)
{
return std::make_tuple(std::get<1 + Is>(tuple)...);
}
template <typename Tuple>
auto pop_front(const Tuple& tuple)
{
return pop_front_impl(tuple,
std::make_index_sequence<std::tuple_size<Tuple>::value - 1>());
}
Demo
Note that it is really basic and doesn't handle tuple of reference, or tuple of const qualified type, but it might be sufficient.
With generic lambdas, you can do quite elegant:
template<typename Tuple>
constexpr auto pop_front(Tuple tuple) {
static_assert(std::tuple_size<Tuple>::value > 0, "Cannot pop from an empty tuple");
return std::apply(
[](auto, auto... rest) { return std::make_tuple(rest...); },
tuple);
}
Length and types of a std::tuple are determined at compile time. No run-time popping or pushing is possible. You could use a std::vector to provide for the run-time modifications.
The data type for your vector could be a std::variant (C++17) or boost::variant. Both types take a list of supported types at compile time, and can be filled with any value of matching type.
Alternatively, you could use std::any (also C++17) or boost::any to store any type, but with different access semantics.
typedef boost::variant<int, std::string, double> value;
std::vector<value> data;
data.push_back(value(42));
data.psuh_back(value(3.14));
You cannot 'lengthen' the tuple by adding elements - that is not what a tuple represents. The point of a tuple is the binding connection between the different values that make it up, like 'firstname', 'lastname', 'phone'.
What you seem to want is much easier accomplished by using a vector - you can easily add elements to it, and remove them - its size can be arbitrarily changed as needed.

Is it possible to write a generic variadic zipWith in C++?

I want a generic zipWith function in C++ of variable arity. I have two problems. The first is that I cannot determine the type of the function pointer passed to zipWith. It must be of the same arity as the number of vectors passed to zipWith and it must accept references to the vectors' element types respectively. The second is that I have no idea how to walk these vectors in parallel to build an argument list, call func(), and bail once the shortest vector is exhausted.
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(???<what goes here>), std::vector<T> first, Vargs rest) {
???
}
I had a long answer, then I changed my mind in a way that made the solution much shorter. But I'm going to show my thought process and give you both answers!
My first step is to determine the proper signature. I don't understand all of it, but you can treat a parameter pack as a comma-separated list of the actual items with the text-dump hidden. You can extend the list on either side by more comma-separated items! So directly applying that:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, Vargs rest) {
???
}
You have to put a "..." after a parameter pack for an expression section to see the expanded list. You have to put one in the regular parameter portion, too:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, Vargs... rest) {
???
}
You said that your function parameters are a bunch of vectors. Here, you're hoping that each of Vargs is really a std::vector. Type transformations can be applied to a parameter pack, so why don't we ensure that you have vectors:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, std::vector<Vargs> ...rest) {
???
}
Vectors can be huge objects, so let's use const l-value references. Also, we could use std::function so we can use lambda or std::bind expressions:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (std::function<R(T, Vargs...)> func, std::vector<T> const &first, std::vector<Vargs> const &...rest) {
???
}
(I ran into problems here from using std::pow for testing. My compiler wouldn't accept a classic function pointer being converted into a std::function object. So I had to wrap it in a lambda. Maybe I should ask here about that....)
At this point, I reloaded the page and saw one response (by pmr). I don't really understand this zipping, folding, exploding, whatever stuff, so I thought his/her solution was too complicated. So I thought about a more direct solution:
template < typename R, typename T, typename ...MoreTs >
std::vector<R>
zip_with( std::function<R(T,MoreTs...)> func,
const std::vector<T>& first, const std::vector<MoreTs>& ...rest )
{
auto const tuples = rearrange_vectors( first, rest... );
std::vector<R> result;
result.reserve( tuples.size() );
for ( auto const &x : tuples )
result.push_back( evaluate(x, func) );
return result;
}
I would create a vector of tuples, where each tuple was made from plucking corresponding elements from each vector. Then I would create a vector of
evaluation results from passing a tuple and func each time.
The rearrange_vectors has to make table of values in advance (default-constructed) and fill out each entry a sub-object at a time:
template < typename T, typename ...MoreTs >
std::vector<std::tuple<T, MoreTs...>>
rearrange_vectors( const std::vector<T>& first,
const std::vector<MoreTs>& ...rest )
{
decltype(rearrange_vectors(first, rest...))
result( first.size() );
fill_vector_perpendicularly<0>( result, first, rest... );
return result;
}
The first part of the first line lets the function access its own return type without copy-and-paste. The only caveat is that r-value reference parameters must be surrounded by std::forward (or move) so a l-value overload of the recursive call doesn't get chosen by mistake. The function that mutates part of each tuple element has to explicitly take the current index. The index moves up by one during parameter pack peeling:
template < std::size_t, typename ...U >
void fill_vector_perpendicularly( std::vector<std::tuple<U...>>& )
{ }
template < std::size_t I, class Seq, class ...MoreSeqs, typename ...U >
void fill_vector_perpendicularly( std::vector<std::tuple<U...>>&
table, const Seq& first, const MoreSeqs& ...rest )
{
auto t = table.begin();
auto const te = table.end();
for ( auto f = first.begin(), fe = first.end(); (te != t) && (fe
!= f) ; ++t, ++f )
std::get<I>( *t ) = *f;
table.erase( t, te );
fill_vector_perpendicularly<I + 1u>( table, rest... );
}
The table is as long as the shortest input vector, so we have to trim the table whenever the current input vector ends first. (I wish I could mark fe as const within the for block.) I originally had first and rest as std::vector, but I realized I could abstract that out; all I need are types that match the standard (sequence) containers in iteration interface. But now I'm stumped on evaluate:
template < typename R, typename T, typename ...MoreTs >
R evaluate( const std::tuple<T, MoreTs...>& x,
std::function<R(T,MoreTs...)> func )
{
//???
}
I can do individual cases:
template < typename R >
R evaluate( const std::tuple<>& x, std::function<R()> func )
{ return func(); }
template < typename R, typename T >
R evaluate( const std::tuple<T>& x, std::function<R(T)> func )
{ return func( std::get<0>(x) ); }
but I can't generalize it for a recursive case. IIUC, std::tuple doesn't support peeling off the tail (and/or head) as a sub-tuple. Nor does std::bind support currying arguments into a function in piecemeal, and its placeholder system isn't compatible with arbitrary-length parameter packs. I wish I could just list each parameter like I could if I had access to the original input vectors....
...Wait, why don't I do just that?!...
...Well, I never heard of it. I've seen transferring a template parameter pack to the function parameters; I just showed it in zipWith. Can I do it from the function parameter list to the function's internals? (As I'm writing, I now remember seeing it in the member-initialization part of class constructors, for non-static members that are arrays or class types.) Only one way to find out:
template < typename R, typename T, typename ...MoreTs >
std::vector<R>
zip_with( std::function<R(T,MoreTs...)> func, const std::vector<T>&
first, const std::vector<MoreTs>& ...rest )
{
auto const s = minimum_common_size( first, rest... );
decltype(zip_with(func,first,rest...)) result;
result.reserve( s );
for ( std::size_t i = 0 ; i < s ; ++i )
result.push_back( func(first[i], rest[i]...) );
return result;
}
where I'm forced to compute the total number of calls beforehand:
inline std::size_t minimum_common_size() { return 0u; }
template < class SizedSequence >
std::size_t minimum_common_size( const SizedSequence& first )
{ return first.size(); }
template < class Seq, class ...MoreSeqs >
std::size_t
minimum_common_size( const Seq& first, const MoreSeqs& ...rest )
{ return std::min( first.size(), minimum_common_size(rest...) ); }
and sure enough, it worked! Of course, this meant that I over-thought the problem just as bad as the other respondent (in a different way). It also means that I unnecessarily bored you with most of this post. As I wrapped this up, I realized that the replacement of std::vector with generic sequence-container types can be applied in zip_width. And I realized that I could reduce the mandatory one vector to no mandatory vectors:
template < typename R, typename ...T, class ...SizedSequences >
std::vector<R>
zip_with( R func(T...) /*std::function<R(T...)> func*/,
SizedSequences const& ...containers )
{
static_assert( sizeof...(T) == sizeof...(SizedSequences),
"The input and processing lengths don't match." );
auto const s = minimum_common_size( containers... );
decltype( zip_with(func, containers...) ) result;
result.reserve( s );
for ( std::size_t i = 0 ; i < s ; ++i )
result.push_back( func(containers[i]...) );
return result;
}
I added the static_assert as I copied the code here, since I forgot to make sure that the func's argument count and the number of input vectors agree. Now I realize that I can fix the dueling function-pointer vs. std::function object by abstracting both away:
template < typename R, typename Func, class ...SizedSequences >
std::vector<R>
zip_with( Func&& func, SizedSequences&& ...containers )
{
auto const s = minimum_common_size( containers... );
decltype( zip_with<R>(std::forward<Func>(func),
std::forward<SizedSequences>(containers)...) ) result;
result.reserve( s );
for ( std::size_t i = 0 ; i < s ; ++i )
result.push_back( func(containers[i]...) );
return result;
}
Marking a function parameter with an r-value reference is the universal passing method. It handles all kinds of references and const/volatile (cv) qualifications. That's why I switched containers to it. The func could have any structure; it can even be a class object with multiple versions of operator (). Since I'm using r-values for the containers, they'll use the best cv-qualification for element dereferencing, and the function can use that for overload resolution. The recursive "call" to internally determine the result type needs to use std::forward to prevent any "downgrades" to l-value references. It also reveals a flaw in this iteration: I must provide the return type.
I'll fix that, but first I want to explain the STL way. You do not pre-determine a specific container type and return that to the user. You ask for a special object, an output-iterator, that you send the results to. The iterator could be connected to a container, of which the standard provides several varieties. It could be connected to an output stream instead, directly printing the results! The iterator method also relieves me from directly worrying about memory concerns.
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <utility>
#include <vector>
inline std::size_t minimum_common_size() { return 0u; }
template < class SizedSequence >
std::size_t minimum_common_size( const SizedSequence& first )
{ return first.size(); }
template < class Seq, class ...MoreSeqs >
std::size_t minimum_common_size( const Seq& first,
const MoreSeqs& ...rest )
{
return std::min<std::size_t>( first.size(),
minimum_common_size(rest...) );
}
template < typename OutIter, typename Func, class ...SizedSequences >
OutIter
zip_with( OutIter o, Func&& func, SizedSequences&& ...containers )
{
auto const s = minimum_common_size( containers... );
for ( std::size_t i = 0 ; i < s ; ++i )
*o++ = func( containers[i]... );
return o;
}
template < typename Func, class ...SizedSequences >
auto zipWith( Func&& func, SizedSequences&& ...containers )
-> std::vector<decltype( func(containers.front()...) )>
{
using std::forward;
decltype( zipWith(forward<Func>( func ), forward<SizedSequences>(
containers )...) ) result;
#if 1
// `std::vector` is the only standard container with the `reserve`
// member function. Using it saves time when doing multiple small
// inserts, since you'll do reallocation at most (hopefully) once.
// The cost is that `s` is already computed within `zip_with`, but
// we can't get at it. (Remember that most container types
// wouldn't need it.) Change the preprocessor flag to change the
// trade-off.
result.reserve( minimum_common_size(containers...) );
#endif
zip_with( std::back_inserter(result), forward<Func>(func),
forward<SizedSequences>(containers)... );
return result;
}
I copied minimum_common_size here, but explicitly mentioned the result type for the least-base case, proofing against different container types using different size types.
Functions taking an output-iterator usually return iterator after all the iterators are done. This lets you start a new output run (even with a different output function) where you left off. It's not critical for the standard output iterators, since they're all pseudo-iterators. It is important when using a forward-iterator (or above) as an output iterator since they do track position. (Using a forward iterator as an output one is safe as long as the maximum number of transfers doesn't exceed the remaining iteration space.) Some functions put the output iterator at the end of the parameter list, others at the beginning; zip_width must use the latter since parameter packs have to go at the end.
Moving to a suffix return type in zipWith makes every part of the function's signature fair game when computing the return type expression. It also lets me know right away if the computation can't be done due to incompatibilities at compile-time. The std::back_inserter function returns a special output-iterator to the vector that adds elements via the push_back member function.
Here is what I cobbled together:
#include <iostream>
#include <vector>
#include <utility>
template<typename F, typename T, typename Arg>
auto fold(F f, T&& t, Arg&& a)
-> decltype(f(std::forward<T>(t), std::forward<Arg>(a)))
{ return f(std::forward<T>(t), std::forward<Arg>(a)); }
template<typename F, typename T, typename Head, typename... Args>
auto fold(F f, T&& init, Head&& h, Args&&... args)
-> decltype(f(std::forward<T>(init), std::forward<Head>(h)))
{
return fold(f, f(std::forward<T>(init), std::forward<Head>(h)),
std::forward<Args>(args)...);
}
// hack in a fold for void functions
struct ignore {};
// cannot be a lambda, needs to be polymorphic on the iterator type
struct end_or {
template<typename InputIterator>
bool operator()(bool in, const std::pair<InputIterator, InputIterator>& p)
{ return in || p.first == p.second; }
};
// same same but different
struct inc {
template<typename InputIterator>
ignore operator()(ignore, std::pair<InputIterator, InputIterator>& p)
{ p.first++; return ignore(); }
};
template<typename Fun, typename OutputIterator,
typename... InputIterators>
void zipWith(Fun f, OutputIterator out,
std::pair<InputIterators, InputIterators>... inputs) {
if(fold(end_or(), false, inputs...)) return;
while(!fold(end_or(), false, inputs...)) {
*out++ = f( *(inputs.first)... );
fold(inc(), ignore(), inputs...);
}
}
template<typename Fun, typename OutputIterator,
typename InputIterator, typename... Rest>
void transformV(Fun f, OutputIterator out, InputIterator begin, InputIterator end,
Rest... rest)
{
if(begin == end) return ;
while(begin != end) {
*out++ = f(*begin, *(rest)... );
fold(inc2(), ignore(), begin, rest...);
}
}
struct ternary_plus {
template<typename T, typename U, typename V>
auto operator()(const T& t, const U& u, const V& v)
-> decltype( t + u + v) // common type?
{ return t + u + v; }
};
int main()
{
using namespace std;
vector<int> a = {1, 2, 3}, b = {1, 2}, c = {1, 2, 3};
vector<int> out;
zipWith(ternary_plus(), back_inserter(out)
, make_pair(begin(a), end(a))
, make_pair(begin(b), end(b))
, make_pair(begin(c), end(c)));
transformV(ternary_plus(), back_inserter(out),
begin(a), end(a), begin(b), begin(c));
for(auto x : out) {
std::cout << x << std::endl;
}
return 0;
}
This is a slightly improved variant over previous versions. As every
good program should, it starts by defining a left-fold.
It still does not solve the problem of iterators packed in pairs.
In stdlib terms this function would be called transform and would
require that only the length of one sequence is specified and the
others be at least as long. I called it transformV here to avoid
name clashes.