Related
How can I execute a mathematical operation between two boost::multi_arrays?
Example of adding two arrays with value type double:
auto array1 = boost::multi_array<double, 2>(boost::extents[10][10]);
auto array2 = boost::multi_array<double, 2>(boost::extents[10][10]);
auto array3 = array1 + array2; //Does not compile
One possibility I know is the Intel IPP library. Adding two matrices can be done with e.g. ippiAdd_. But Intel IPP does unfortunately not support double values for adding.
So does someone know another library than Intel IPP respectively a solution to overcome the shortcomings of restricted value types in Intel IPP?
You could "just write it":
namespace ArrayOperators {
template <typename L, typename R>
static inline auto operator+(L const& l, R const& r) {
return ArrayOp {std::plus<>{}} (l, r); }
template <typename L, typename R>
static inline auto operator-(L const& l, R const& r) {
return ArrayOp {std::minus<>{}} (l, r); }
template <typename L, typename R>
static inline auto operator/(L const& l, R const& r) {
return ArrayOp {std::divides<>{}} (l, r); }
template <typename L, typename R>
static inline auto operator*(L const& l, R const& r) {
return ArrayOp {std::multiplies<>{}} (l, r); }
}
Of course, this requires us to actually implement the ArrayOp calleable. I took the liberty to
implement it for heterogeneous arrays (so when left and right hand have different element type)
implement it for the case where the right-hand side is not an array, in which case the scalar operand will be applied to every element of the left-hand-side
I didn't support
in-place operations
array ref/array (const) view
arrays of differing shapes or dimensionality
Here goes:
template <typename Op> struct ArrayOp {
Op op;
explicit ArrayOp(Op op) : op(op) {}
template <typename T, typename Scalar, size_t Dim> auto operator()(
boost::multi_array<T, Dim> const& l,
Scalar const& v) const
{
std::array<int, Dim> shape;
std::copy_n(l.shape(), Dim, shape.data());
using R = boost::multi_array<decltype(op(T{}, v)), Dim>;
R result(shape);
std::transform(
l.data(), l.data()+l.num_elements(),
result.data(),
[&op=op,v](auto const& el) { return op(el, v); });
return result;
}
template <typename T, typename U, size_t Dim> auto operator()(
boost::multi_array<T, Dim> const& l,
boost::multi_array<U, Dim> const& r) const
{
std::array<int, Dim> shape;
std::copy_n(l.shape(), Dim, shape.data());
assert(std::equal(shape.begin(), shape.end(), r.shape()));
using R = boost::multi_array<decltype(op(T{}, U{})), Dim>;
R result(shape);
std::transform(
l.data(), l.data()+l.num_elements(),
r.data(), result.data(),
[&op=op](auto const& v1, auto const& v2) { return op(v1, v2); });
return result;
}
};
Basically it comes down to
deduce resulting array element type and shape
do a unary or binary transform (depending on scalar/array rhs)
Now we can write the program:
Live On Compiler Explorer
int main() {
using MA = boost::multi_array<int, 2>;
auto shape = boost::extents[3][3];
MA array1(shape), array2(shape);
std::generate_n(array1.data(), array1.num_elements(),
[n = 0]() mutable { return n+=100; });
std::generate_n(array2.data(), array2.num_elements(),
[n = 0]() mutable { return n+=1; });
fmt::print("array1:\n\t{}\n", fmt::join(array1,"\n\t"));
fmt::print("array2:\n\t{}\n", fmt::join(array2,"\n\t"));
using namespace ArrayOperators;
auto array3 = (array1 + array2)/100.0;
fmt::print("array3:\n\t{}\n", fmt::join(array3,"\n\t"));
}
And it prints
array1:
{100, 200, 300}
{400, 500, 600}
{700, 800, 900}
array2:
{1, 2, 3}
{4, 5, 6}
{7, 8, 9}
array3:
{1.01, 2.02, 3.03}
{4.04, 5.05, 6.06}
{7.07, 8.08, 9.09}
BUT WAIT, WHAT ARE YOU SOLVING
If you want matrix (not "array") operations use Boost uBlas, Eigen, Armadillo
If you want utmost perf, using SIMD/AVX2/GPU instructions, you can use Boost Compute
You have to overload the + operator for those your types of objects boost::multi_array<double, 2> with your desired implementation.
EDIT I just tried real-quick, apparently it was not so hard, but maybe needs more testing and review ;)
Here you go:
boost::multi_array<double, 2> operator+(boost::multi_array<double, 2> a, boost::multi_array<double, 2> b) {
boost::multi_array<double, 2> result = a;
for (size_t i=0; i<a.size(); ++i) {
for (size_t j=0; j<a[i].size(); ++j) {
result[i][j] = a[i][j] + b[i][j];
}
}
return result;
}
Suppose I have a std::vector<std::vector<double>> d and want to assign it to a std::vector<std::vector<int>> i; the best I could come up with was:
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<vector<double>> d = { {1.0, 2.0}, {3.0, 4.0} };
vector<vector<int>> i;
for_each(begin(d), end(d), [&i](vector<double> &x) {
i.emplace_back(begin(x), end(x));
}
);
return 0;
}
If both vectors were using the same type internally, I could just use the assignment operator (see C++ copying multidimensional vector):
i = d;
If the vectors were storing different types internally, but one-dimensional, I could do:
i.assign(begin(d), end(d));
Both of those are really obvious in their intention, which I don't feel is the case with my solution for the multi-dimensional approach. Is there a better way, or an accepted idiom, to do this?
It seems to me that your solution for the 2D vector is a good one.
The problem arises when you have to copy a N-dimension vectors of vectors of vectors...
Suppose you want a function copy_multi_vec() that works in a case as follows
std::vector<std::vector<std::vector<double>>> vvvd
{ { {1.0, 2.0, 3.0}, { 4.0, 5.0, 6.0} },
{ {7.0, 8.0, 9.0}, {10.0, 11.0, 12.0} } };
std::vector<std::vector<std::vector<int>>> vvvi;
copy_multi_vec(vvvi, vvvd);
In this case you can use partial template specialization in an helper class; by example
template <typename T1, typename T2>
struct cmvH
{ static void func (T1 & v1, T2 const & v2) { v1 = v2; } };
template <typename T1, typename T2>
struct cmvH<std::vector<T1>, std::vector<T2>>
{
static void func (std::vector<T1> & v1, std::vector<T2> const & v2)
{
v1.resize( v2.size() );
std::size_t i { 0U };
for ( auto const & e2 : v2 )
cmvH<T1, T2>::func(v1[i++], e2);
}
};
template <typename T1, typename T2>
void copy_multi_vec (T1 & v1, T2 const & v2)
{ cmvH<T1, T2>::func(v1, v2); }
or, if you want use the assign() method for the last level, you can define the helper struct as follows
template <typename, typename>
struct cmvH;
template <typename T1, typename T2>
struct cmvH<std::vector<T1>, std::vector<T2>>
{
static void func (std::vector<T1> & v1, std::vector<T2> const & v2)
{
v1.resize( v2.size() );
v1.assign( v2.cbegin(), v2.cend() );
}
};
template <typename T1, typename T2>
struct cmvH<std::vector<std::vector<T1>>, std::vector<std::vector<T2>>>
{
static void func (std::vector<std::vector<T1>> & v1,
std::vector<std::vector<T2>> const & v2)
{
v1.resize( v2.size() );
std::size_t i { 0U };
for ( auto const & e2 : v2 )
cmvH0<std::vector<T1>, std::vector<T2>>::func(v1[i++], e2);
}
};
Is there a better way, or an accepted idiom, to do this?
There is no getting away from assigning one element of the array at a time. The best you can do is create a function to help with it.
For example, you could use:
template <typename T1, typename T2>
void vector_copy(std::vector<std::vector<T1>>& dest,
std::vector<std::vector<T2>> const& src)
{
// Add the code to do the copy
}
and then, use
vector_copy(d, i);
Consider the following: (Wandbox)
#include <array>
#include <algorithm>
#include <iostream>
template<typename T, int N, int M>
auto concat(const std::array<T, N>& ar1, const std::array<T, M>& ar2)
{
std::array<T, N+M> result;
std::copy (ar1.cbegin(), ar1.cend(), result.begin());
std::copy (ar2.cbegin(), ar2.cend(), result.begin() + N);
return result;
}
int main()
{
std::array<int, 3> ar1 = {1, 2, 3};
std::array<int, 2> ar2 = {4, 5};
auto result = concat<int, 3, 2>(ar1, ar2);
for (auto& x : result)
std::cout << x << " ";
std::cout << std::endl;
return 0;
}
Given a sequence of std::array<T, length1>, std::array<T, length2>, ..., std::array<T, lengthK>, how can I generalize the above code and write a function which concatenates the sequence into an std::array<T, sum(lengths)>?
It would be nice if there is a way to write a reusable function which reduces a similar sequence of template classes using a given binary operation, e.g., use concat in the example above, rather than writing a special method (which would have to be re-written each time the binary op changes).
(IIUC, the relevant Standard Library algorithms (accumulate, reduce) only work in case the class of the result of the binary operation is always the same.)
Here is a simple C++17 solution via fold expressions:
#include <array>
#include <algorithm>
template <typename Type, std::size_t... sizes>
auto concatenate(const std::array<Type, sizes>&... arrays)
{
std::array<Type, (sizes + ...)> result;
std::size_t index{};
((std::copy_n(arrays.begin(), sizes, result.begin() + index), index += sizes), ...);
return result;
}
Example of using:
const std::array<int, 3> array1 = {{1, 2, 3}};
const std::array<int, 2> array2 = {{4, 5}};
const std::array<int, 4> array3 = {{6, 7, 8, 9}};
const auto result = concatenate(array1, array2, array3);
Live demo
You may do the following:
template <typename F, typename T, typename T2>
auto func(F f, T&& t, T2&& t2)
{
return f(std::forward<T>(t), std::forward<T2>(t2));
}
template <typename F, typename T, typename T2, typename ... Ts>
auto func(F f, T&& t, T2&& t2, Ts&&...args)
{
return func(f, f(std::forward<T>(t), std::forward<T2>(t2)), std::forward<Ts>(args)...);
}
With usage
struct concatenater
{
template<typename T, std::size_t N, std::size_t M>
auto operator()(const std::array<T, N>& ar1, const std::array<T, M>& ar2) const
{
std::array<T, N+M> result;
std::copy (ar1.cbegin(), ar1.cend(), result.begin());
std::copy (ar2.cbegin(), ar2.cend(), result.begin() + N);
return result;
}
};
and
auto result = func(concatenater{}, ar1, ar2, ar3, ar4);
C++14 Demo
C++11 Demo
Given a sequence of std::array<T, length1>, std::array<T, length2>, ..., std::array<T, lengthK>, how can I write a function which concatenates the sequence into an std::array<T, sum(lengths)>?
Here's a C++17 solution. It can very probably be shortened and improved, working on it.
template <std::size_t Last = 0, typename TF, typename TArray, typename... TRest>
constexpr auto with_acc_sizes(TF&& f, const TArray& array, const TRest&... rest)
{
f(array, std::integral_constant<std::size_t, Last>{});
if constexpr(sizeof...(TRest) != 0)
{
with_acc_sizes<Last + std::tuple_size_v<TArray>>(f, rest...);
}
}
template<typename T, std::size_t... Sizes>
constexpr auto concat(const std::array<T, Sizes>&... arrays)
{
std::array<T, (Sizes + ...)> result{};
with_acc_sizes([&](const auto& arr, auto offset)
{
std::copy(arr.begin(), arr.end(), result.begin() + offset);
}, arrays...);
return result;
}
Usage:
std::array<int, 3> ar1 = {1, 2, 3};
std::array<int, 2> ar2 = {4, 5};
std::array<int, 3> ar3 = {6, 7, 8};
auto result = concat(ar1, ar2, ar3);
live wandbox example
Works with both g++7 and clang++5.
My first line of reasoning would be to consider converting the array to a tuple of references (a tie), manipulate with tuple_cat and then perform whatever operation is necessary to build the final array (i.e. either move or copy - depending on the arguments originally passed in):
#include <array>
#include <iostream>
namespace detail {
template<class Array, std::size_t...Is>
auto array_as_tie(Array &a, std::index_sequence<Is...>) {
return std::tie(a[Is]...);
};
template<class T, class Tuple, std::size_t...Is>
auto copy_to_array(Tuple &t, std::index_sequence<Is...>) {
return std::array<T, sizeof...(Is)>
{
std::get<Is>(t)...
};
};
template<class T, class Tuple, std::size_t...Is>
auto move_to_array(Tuple &t, std::index_sequence<Is...>) {
return std::array<T, sizeof...(Is)>
{
std::move(std::get<Is>(t))...
};
};
}
template<class T, std::size_t N>
auto array_as_tie(std::array<T, N> &a) {
return detail::array_as_tie(a, std::make_index_sequence<N>());
};
// various overloads for different combinations of lvalues and rvalues - needs some work
// for instance, does not handle mixed lvalues and rvalues yet
template<class T, std::size_t N1, std::size_t N2>
auto array_cat(std::array<T, N1> &a1, std::array<T, N2> &a2) {
auto tied = std::tuple_cat(array_as_tie(a1), array_as_tie(a2));
return detail::copy_to_array<T>(tied, std::make_index_sequence<N1 + N2>());
};
template<class T, std::size_t N1, std::size_t N2>
auto array_cat(std::array<T, N1> &&a1, std::array<T, N2> &&a2) {
auto tied = std::tuple_cat(array_as_tie(a1), array_as_tie(a2));
return detail::move_to_array<T>(tied, std::make_index_sequence<N1 + N2>());
};
int main() {
std::array<int, 3> ar1 = {1, 2, 3};
std::array<int, 2> ar2 = {4, 5};
auto result = array_cat(ar1, ar2);
for (auto &x : result)
std::cout << x << " ";
std::cout << std::endl;
// move-construction
auto r2 = array_cat(std::array<std::string, 2> {"a", "b"},
std::array<std::string, 2>{"asdfghjk", "qwertyui"});
std::cout << "string result:\n";
for (auto &&x : r2)
std::cout << x << " ";
std::cout << std::endl;
return 0;
}
expected results:
1 2 3 4 5
string result:
a b asdfghjk qwertyui
A more concise evolution of #Constructor's C++17 solution with the added benefit that Type is not required to be default constructible
template <typename Type, std::size_t... sizes>
constexpr auto concatenate(const std::array<Type, sizes>&... arrays)
{
return std::apply(
[] (auto... elems) -> std::array<Type, (sizes + ...)> { return {{ elems... }}; },
std::tuple_cat(std::tuple_cat(arrays)...));
}
C++14.
template<std::size_t I>
using index_t=std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( index<Is>... );
};
}
template<std::size_t N>
auto index_upto(index_t<N>={}){
return index_over(std::make_index_sequence<N>{});
}
this lets us expand parameter packs inline.
template<std::size_t, class T>
using indexed_type=T;
template<class T>
std::decay_t<T> concat_arrays( T&& in ){ return std::forward<T>(in); }
template<class T, std::size_t N0, std::size_t N1 >
std::array<T, N0+N1>
concat_arrays( std::array<T,N0> arr0, std::array<T,N1> arr1 ){
auto idx0 = index_upto<N0>();
auto idx1 = index_upto<N1>();
return idx0( [&](auto...I0s){
return idx1( [&](auto...I1s)->std::array<T, N0+N1>{
return {{
arr0[I0s]...,
arr1[I1s]...
}};
})
});
}
which gets us to two. For N, the easy way is:
template<class T, std::size_t N0, std::size_t N1, std::size_t...Ns >
auto concat_arrays( std::array<T,N0> arr0, std::array<T,N1> arr1, std::array<T, Ns>... arrs ){
return concat_arrays( std::move(arr0), concat_arrays( std::move(arr1), std::move(arrs)... ) );
}
but it should be possible without recursion.
Code not tested.
C++17 solution that is constexpr and works correctly with moveably-only types.
template<class Array>
inline constexpr auto array_size = std::tuple_size_v<std::remove_reference_t<Array>>;
template<typename... Ts>
constexpr auto make_array(Ts&&... values)
{
using T = std::common_type_t<Ts...>;
return std::array<T, sizeof...(Ts)>{static_cast<T>(std::forward<Ts>(values))...};
}
namespace detail
{
template<typename Arr1, typename Arr2, std::size_t... is1, std::size_t... is2>
constexpr auto array_cat(Arr1&& arr1, Arr2&& arr2, std::index_sequence<is1...>, std::index_sequence<is2...>)
{
return make_array(std::get<is1>(std::forward<Arr1>(arr1))...,
std::get<is2>(std::forward<Arr2>(arr2))...);
}
}
template<typename Arr, typename... Arrs>
constexpr auto array_cat(Arr&& arr, Arrs&&... arrs)
{
if constexpr (sizeof...(Arrs) == 0)
return std::forward<Arr>(arr);
else if constexpr (sizeof...(Arrs) == 1)
return detail::array_cat(std::forward<Arr>(arr), std::forward<Arrs>(arrs)...,
std::make_index_sequence<array_size<Arr>>{},
std::make_index_sequence<array_size<Arrs...>>{});
else
return array_cat(std::forward<Arr>(arr), array_cat(std::forward<Arrs>(arrs)...));
}
Strictly C++11; not as readable as #Jarod42's, but potentially much more efficient with many arrays if the call-tree isn't fully flattened (in terms of inlining) since only one result object exists rather than multiple temporary, progressively-growing result objects:
namespace detail {
template<std::size_t...>
struct sum_sizes_;
template<std::size_t Acc>
struct sum_sizes_<Acc> : std::integral_constant<std::size_t, Acc> { };
template<std::size_t Acc, std::size_t N, std::size_t... Ns>
struct sum_sizes_<Acc, N, Ns...> : sum_sizes_<Acc + N, Ns...> { };
template<typename... As>
using sum_sizes_t = typename sum_sizes_<
0, std::tuple_size<typename std::decay<As>::type>{}...
>::type;
template<std::size_t O, typename A, typename R>
void transfer(R& ret, typename std::remove_reference<A>::type const& a) {
std::copy(a.begin(), a.end(), ret.begin() + O);
}
template<std::size_t O, typename A, typename R>
void transfer(R& ret, typename std::remove_reference<A>::type&& a) {
std::move(a.begin(), a.end(), ret.begin() + O);
}
template<std::size_t, typename R>
void concat(R const&) { }
template<std::size_t O, typename R, typename A, typename... As>
void concat(R& ret, A&& a, As&&... as) {
transfer<O, A>(ret, std::forward<A>(a));
concat<(O + sum_sizes_t<A>{})>(ret, std::forward<As>(as)...);
}
}
template<typename... As, typename std::enable_if<(sizeof...(As) >= 2), int>::type = 0>
auto concat(As&&... as)
-> std::array<
typename std::common_type<typename std::decay<As>::type::value_type...>::type,
detail::sum_sizes_t<As...>{}
> {
decltype(concat(std::forward<As>(as)...)) ret;
detail::concat<0>(ret, std::forward<As>(as)...);
return ret;
}
Online Demo
Note that this also forwards properly by using the std::move algorithm for rvalues rather than std::copy.
This doesn't generalize, but takes advantage of the fact that if we splat two arrays inside a set of braces, we can use that to initialize a new array.
I'm not sure how useful generalizing is, in any case. Given a bunch of arrays of mismatched sizes, just what else is there to do with them but join them all together?
#include <array>
#include <iostream>
#include <utility>
template<typename T, std::size_t L, std::size_t... Ls,
std::size_t R, std::size_t... Rs>
constexpr std::array<T, L + R> concat_aux(const std::array<T, L>& l, std::index_sequence<Ls...>,
const std::array<T, R>& r, std::index_sequence<Rs...>) {
return std::array<T, L + R> { std::get<Ls>(l)..., std::get<Rs>(r)... };
}
template<typename T, std::size_t L, std::size_t R>
constexpr std::array<T, L + R> concat(const std::array<T, L>& l, const std::array<T, R>& r) {
return concat_aux(l, std::make_index_sequence<L>{},
r, std::make_index_sequence<R>{});
}
template<typename T, std::size_t L, std::size_t R, std::size_t... Sizes>
constexpr auto concat(const std::array<T, L>& l,
const std::array<T, R>& r,
const std::array<T, Sizes>&... arrays) {
return concat(concat(l, r), arrays...);
}
int main() {
std::array<int, 5> a1{1, 2, 3, 4, 5};
std::array<int, 3> a2{6, 7, 8};
std::array<int, 2> a3{9, 10};
for (const auto& elem : concat(a1, a2, a3)) {
std::cout << elem << " ";
}
}
The function transform conducted by
const std::vector<int> a = {1, 2, 3, 4, 5};
const std::vector<double> b = {1.2, 4.5, 0.6};
const std::vector<std::string> c = {"hi", "howdy", "hello", "bye"};
std::vector<double> result(5);
transform<Foo> (result.begin(),
a.begin(), a.end(),
b.begin(), b.end(),
c.begin(), c.end());
is to carry out a generalization of std::transform on multiple containers, outputing the results in the vector result. One function with signature (int, double, const std::string&) would apparently be needed to handle the three containers in this example. But because the containers have different lengths, we instead need to use some overloads. I will test this using these member overloads of a holder class Foo:
static int execute (int i, double d, const std::string& s) {return i + d + s.length();}
static int execute (int i, const std::string& s) {return 2 * i + s.length();}
static int execute (int i) {return 3 * i - 1;}
However, the program will not compile unless I define three other overloads that are never even called, namely with arguments (int, double), (const std::string&) and (). I want to remove these overloads, but the program won't let me. You can imagine the problem this would cause if we had more than 3 containers (of different lengths), forcing overloads with many permutations of arguments to be defined when they are not even being used.
Here is my working program that will apparently show why these extraneous overloads are needed. I don't see how or why the are forced to be defined, and want to remove them. Why must they be there, and how to remove the need for them?
#include <iostream>
#include <utility>
#include <tuple>
bool allTrue (bool a) {return a;}
template <typename... B>
bool allTrue (bool a, B... b) {return a && allTrue(b...);}
template <typename F, size_t... Js, typename Tuple>
typename F::return_type screenArguments (std::index_sequence<>, std::index_sequence<Js...>, Tuple& tuple) {
return F::execute (*std::get<Js>(tuple)++...);
}
// Thanks to Barry for coming up with screenArguments.
template <typename F, std::size_t I, size_t... Is, size_t... Js, typename Tuple>
typename F::return_type screenArguments (std::index_sequence<I, Is...>, std::index_sequence<Js...>, Tuple& tuple) {
if (std::get<2*I>(tuple) != std::get<2*I+1>(tuple))
return screenArguments<F> (std::index_sequence<Is...>{}, std::index_sequence<Js..., 2*I>{}, tuple);
else
return screenArguments<F> (std::index_sequence<Is...>{}, std::index_sequence<Js...>{}, tuple);
}
template <typename F, typename Tuple>
typename F::return_type passCertainArguments (Tuple& tuple) {
return screenArguments<F> (std::make_index_sequence<std::tuple_size<Tuple>::value / 2>{},
std::index_sequence<>{}, tuple);
}
template <typename F, typename OutputIterator, std::size_t... Is, typename... InputIterators>
OutputIterator transformHelper (OutputIterator result, const std::index_sequence<Is...>&, InputIterators... iterators) {
auto tuple = std::make_tuple(iterators...);
while (!allTrue(std::get<2*Is>(tuple) == std::get<2*Is + 1>(tuple)...))
*result++ = passCertainArguments<F>(tuple);
return result;
}
template <typename F, typename OutputIterator, typename... InputIterators>
OutputIterator transform (OutputIterator result, InputIterators... iterators) {
return transformHelper<F> (result, std::make_index_sequence<sizeof...(InputIterators) / 2>{}, iterators...);
}
// Testing
#include <vector>
struct Foo {
using return_type = int;
static int execute (int i, double d, const std::string& s) {return i + d + s.length();}
static int execute (int i, const std::string& s) {return 2 * i + s.length();}
static int execute (int i) {return 3 * i - 1;}
// These overloads are never called, but apparently must still be defined.
static int execute () {std::cout << "Oveload4 called.\n"; return 0;}
static int execute (int i, double d) {std::cout << "Oveload5 called.\n"; return i + d;}
static int execute (const std::string& s) {std::cout << "Oveload6 called.\n"; return s.length();}
};
int main() {
const std::vector<int> a = {1, 2, 3, 4, 5};
const std::vector<double> b = {1.2, 4.5, 0.6};
const std::vector<std::string> c = {"hi", "howdy", "hello", "bye"};
std::vector<double> result(5);
transform<Foo> (result.begin(),
a.begin(), a.end(),
b.begin(), b.end(),
c.begin(), c.end());
for (double x : result) std::cout << x << ' '; std::cout << '\n';
// 4 11 8 11 14 (correct output)
}
The compiler does not know at compile time which combinations of functions will be used in runtime. So you have to implement all the 2^N functions for every combination. Also your approach will not work when you have containers with the same types.
If you want to stick with templates, my idea is to implement the function something like this:
template <bool Arg1, bool Arg2, bool Arg3>
static int execute (int *i, double *d, const std::string *s);
The template arguments Arg1, Arg2, Arg3 represent the validity of each parameter. The compiler will automatically generate all the 2^N implementations for every parameter combination. Feel free to use if statements inside this function instead of template specialization - they will be resolved at compile time to if (true) or if (false).
I think I got! Template the function Foo::execute according to the number of arguments that are actually needed, and let them all have the same arguments:
struct Foo {
using return_type = int;
template <std::size_t> static return_type execute (int, double, const std::string&);
};
template <> Foo::return_type Foo::execute<3> (int i, double d, const std::string& s) {return i + d + s.length();}
template <> Foo::return_type Foo::execute<2> (int i, double, const std::string& s) {return 2 * i + s.length();}
template <> Foo::return_type Foo::execute<1> (int i, double, const std::string&) {return 3 * i - 1;}
template <> Foo::return_type Foo::execute<0> (int, double, const std::string&) {return 0;} // The only redundant specialization that needs to be defined.
Here is the full solution.
#include <iostream>
#include <utility>
#include <tuple>
#include <iterator>
bool allTrue (bool b) {return b;}
template <typename... Bs>
bool allTrue (bool b, Bs... bs) {return b && allTrue(bs...);}
template <typename F, std::size_t N, typename Tuple, typename... Args>
typename F::return_type countArgumentsNeededAndExecute (Tuple&, const std::index_sequence<>&, Args&&... args) {
return F::template execute<N>(std::forward<Args>(args)...);
}
template <typename F, std::size_t N, typename Tuple, std::size_t I, size_t... Is, typename... Args>
typename F::return_type countArgumentsNeededAndExecute (Tuple& tuple, const std::index_sequence<I, Is...>&, Args&&... args) { // Pass tuple by reference, because its iterator elements will be modified (by being incremented).
return (std::get<2*I>(tuple) != std::get<2*I + 1>(tuple)) ?
countArgumentsNeededAndExecute<F, N+1> (tuple, std::index_sequence<Is...>{}, std::forward<Args>(args)..., // The number of arguments to be used increases by 1.
*std::get<2*I>(tuple)++) : // Pass the value that will be used and increment the iterator.
countArgumentsNeededAndExecute<F, N> (tuple, std::index_sequence<Is...>{}, std::forward<Args>(args)...,
typename std::iterator_traits<typename std::tuple_element<2*I, Tuple>::type>::value_type{}); // Pass the default value (it will be ignored anyway), and don't increment the iterator. Hence, the number of arguments to be used does not change.
}
template <typename F, typename OutputIterator, std::size_t... Is, typename... InputIterators>
OutputIterator transformHelper (OutputIterator result, const std::index_sequence<Is...>& indices, InputIterators... iterators) {
auto tuple = std::make_tuple(iterators...); // Cannot be const, as the iterators are being incremented.
while (!allTrue(std::get<2*Is>(tuple) == std::get<2*Is + 1>(tuple)...))
*result++ = countArgumentsNeededAndExecute<F, 0> (tuple, indices); // Start the count at 0. Examine 'indices', causing the count to increase one by one.
return result;
}
template <typename F, typename OutputIterator, typename... InputIterators>
OutputIterator transform (OutputIterator result, InputIterators... iterators) {
return transformHelper<F> (result, std::make_index_sequence<sizeof...(InputIterators) / 2>{}, iterators...);
}
// Testing
#include <vector>
struct Foo {
using return_type = int;
template <std::size_t> static return_type execute (int, double, const std::string&);
};
// Template the function Foo::execute according to the number of arguments that are actually needed:
template <> Foo::return_type Foo::execute<3> (int i, double d, const std::string& s) {return i + d + s.length();}
template <> Foo::return_type Foo::execute<2> (int i, double, const std::string& s) {return 2 * i + s.length();}
template <> Foo::return_type Foo::execute<1> (int i, double, const std::string&) {return 3 * i - 1;}
template <> Foo::return_type Foo::execute<0> (int, double, const std::string&) {return 0;} // The only redundant specialization that needs to be defined.
int main() {
const std::vector<int> a = {1, 2, 3, 4, 5};
const std::vector<double> b = {1.2, 4.5, 0.6};
const std::vector<std::string> c = {"hi", "howdy", "hello", "bye"};
std::vector<double> result(5);
transform<Foo> (result.begin(),
a.begin(), a.end(),
b.begin(), b.end(),
c.begin(), c.end());
for (double x : result) std::cout << x << ' '; std::cout << '\n';
// 4 11 8 11 14 (correct output)
}
And a second solution using Andrey Nasonov's bool templates to generate all 2^N overloads needed. Note that the above solution requires only N+1 template instantations for the overloads though.
#include <iostream>
#include <utility>
#include <tuple>
bool allTrue (bool b) {return b;}
template <typename... Bs>
bool allTrue (bool b, Bs... bs) {return b && allTrue(bs...);}
template <bool...> struct BoolPack {};
template <typename F, typename Tuple, bool... Bs, typename... Args>
typename F::return_type checkArgumentsAndExecute (const Tuple&, const std::index_sequence<>&, BoolPack<Bs...>, Args&&... args) {
return F::template execute<Bs...>(std::forward<Args>(args)...);
}
template <typename F, typename Tuple, std::size_t I, size_t... Is, bool... Bs, typename... Args>
typename F::return_type checkArgumentsAndExecute (Tuple& tuple, const std::index_sequence<I, Is...>&, BoolPack<Bs...>, Args&&... args) { // Pass tuple by reference, because its iterators elements will be modified (by being incremented).
return (std::get<2*I>(tuple) != std::get<2*I+1>(tuple)) ?
checkArgumentsAndExecute<F> (tuple, std::index_sequence<Is...>{}, BoolPack<Bs..., true>{}, std::forward<Args>(args)...,
*std::get<2*I>(tuple)++) : // Pass the value that will be used and increment the iterator.
checkArgumentsAndExecute<F> (tuple, std::index_sequence<Is...>{}, BoolPack<Bs..., false>{}, std::forward<Args>(args)...,
typename std::iterator_traits<typename std::tuple_element<2*I, Tuple>::type>::value_type{}); // Pass the default value (it will be ignored anyway), and don't increment the iterator.
}
template <typename F, typename OutputIterator, std::size_t... Is, typename... InputIterators>
OutputIterator transformHelper (OutputIterator& result, const std::index_sequence<Is...>& indices, InputIterators... iterators) {
auto tuple = std::make_tuple(iterators...); // Cannot be const, as the iterators are being incremented.
while (!allTrue(std::get<2*Is>(tuple) == std::get<2*Is + 1>(tuple)...))
*result++ = checkArgumentsAndExecute<F> (tuple, indices, BoolPack<>{});
return result;
}
template <typename F, typename OutputIterator, typename... InputIterators>
OutputIterator transform (OutputIterator result, InputIterators... iterators) {
return transformHelper<F> (result, std::make_index_sequence<sizeof...(InputIterators) / 2>{}, iterators...);
}
// Testing
#include <vector>
struct Foo {
using return_type = int;
template <bool B1, bool B2, bool B3> static return_type execute (int, double, const std::string&) {return 0;} // All necessary overloads defined at once here.
};
// Specializations of Foo::execute<B1,B2,B3>(int, double, const std::string&) that will actually be called by transform<Foo> (it is the client's responsibility to define these overloads based on the containers passed to transform<Foo>).
template <> Foo::return_type Foo::execute<true, true, true> (int i, double d, const std::string& s) {return i + d + s.length();}
template <> Foo::return_type Foo::execute<true, false, true> (int i, double, const std::string& s) {return 2 * i + s.length();}
template <> Foo::return_type Foo::execute<true, false, false> (int i, double, const std::string&) {return 3 * i - 1;}
int main() {
const std::vector<int> a = {1, 2, 3, 4, 5};
const std::vector<double> b = {1.2, 4.5, 0.6};
const std::vector<std::string> c = {"hi", "howdy", "hello", "bye"};
std::vector<double> result(5);
transform<Foo> (result.begin(),
a.begin(), a.end(),
b.begin(), b.end(),
c.begin(), c.end());
for (double x : result) std::cout << x << ' '; std::cout << '\n';
// 4 11 8 11 14 (correct output)
}
While learning about template parameter packs, I'm trying to write a clever, simple function to efficiently append two or more std::vector containers together.
Below are two initial solutions.
Version 1 is elegant but buggy, as it relies on side-effects during the expansion of the parameter pack, and the order of evaluation is undefined.
Version 2 works, but relies on a helper function that requires two cases. Yuck.
Can you see if you can come up with a simpler solution?
(For efficiency, the vector data should not be copied more than once.)
#include <vector>
#include <iostream>
// Append all elements of v2 to the end of v1.
template<typename T>
void append_to_vector(std::vector<T>& v1, const std::vector<T>& v2) {
for (auto& e : v2) v1.push_back(e);
}
// Expand a template parameter pack for side effects.
template<typename... A> void ignore_all(const A&...) { }
// Version 1: Concatenate two or more std::vector<> containers into one.
// Nicely simple, but buggy as the order of evaluation is undefined.
template<typename T, typename... A>
std::vector<T> concat1(std::vector<T> v1, const A&... vr) {
// Function append_to_vector() returns void, so I enclose it in (..., 1).
ignore_all((append_to_vector(v1, vr), 1)...);
// In fact, the evaluation order is right-to-left in gcc and MSVC.
return v1;
}
// Version 2:
// It works but looks ugly.
template<typename T, typename... A>
void concat2_aux(std::vector<T>& v1, const std::vector<T>& v2) {
append_to_vector(v1, v2);
}
template<typename T, typename... A>
void concat2_aux(std::vector<T>& v1, const std::vector<T>& v2, const A&... vr) {
append_to_vector(v1, v2);
concat2_aux(v1, vr...);
}
template<typename T, typename... A>
std::vector<T> concat2(std::vector<T> v1, const A&... vr) {
concat2_aux(v1, vr...);
return v1;
}
int main() {
const std::vector<int> v1 { 1, 2, 3 };
const std::vector<int> v2 { 4 };
const std::vector<int> v3 { 5, 6 };
for (int i : concat1(v1, v2, v3)) std::cerr << " " << i;
std::cerr << "\n"; // gcc output is: 1 2 3 5 6 4
for (int i : concat2(v1, v2, v3)) std::cerr << " " << i;
std::cerr << "\n"; // gcc output is: 1 2 3 4 5 6
}
A helper type: I dislike using intfor it.
struct do_in_order { template<class T>do_in_order(T&&){}};
Add up sizes:'
template<class V>
std::size_t sum_size( std::size_t& s, V&& v ) {return s+= v.size(); }
Concat. Returns type to be ignored:
template<class V>
do_in_order concat_helper( V& lhs, V const& rhs ) { lhs.insert( lhs.end(), rhs.begin(), rhs.end() ); return {}; }
Micro optimization, and lets you concat vectors of move only types:
template<class V>
do_in_order concat_helper( V& lhs, V && rhs ) { lhs.insert( lhs.end(), std::make_move_iterator(rhs.begin()), std::make_move_iterator(rhs.end()) ); return{}; }
actual function. Above stuff should be in a details namespace:
template< typename T, typename A, typename... Vs >
std::vector<T,A> concat( std::vector<T,A> lhs, Vs&&...vs ){
std::size s=lhs.size();
do_in_order _0[]={ sum_size(s,vs)..., 0 };
lhs.reserve(s);
do_in_order _1[]={ concat_helper( lhs, std::forward<Vs>(vs) )..., 0 };
return std::move(lhs); // rvo blocked
}
apologies for any typos.
There is a related answer on concatenation of strings: https://stackoverflow.com/a/21806609/1190077 .
Adapted here, it looks like:
template<typename T, typename... A>
std::vector<T> concat_version3(std::vector<T> v1, const A&... vr) {
int unpack[] { (append_to_vector(v1, vr), 0)... };
(void(unpack));
return v1;
}
This seems to work!
However, is the evaluation order of the template parameter pack now well-defined, or is it by accident that the compiler did the right thing?
The answer by Yakk (https://stackoverflow.com/a/23439527/1190077) works well.
Here is a polished version, incorporating my improvement to do_in_order and removing the sum_size external function:
// Nice syntax to allow in-order expansion of parameter packs.
struct do_in_order {
template<typename T> do_in_order(std::initializer_list<T>&&) { }
};
namespace details {
template<typename V> void concat_helper(V& l, const V& r) {
l.insert(l.end(), r.begin(), r.end());
}
template<class V> void concat_helper(V& l, V&& r) {
l.insert(l.end(), std::make_move_iterator(r.begin()),
std::make_move_iterator(r.end()));
}
} // namespace details
template<typename T, typename... A>
std::vector<T> concat(std::vector<T> v1, A&&... vr) {
std::size_t s = v1.size();
do_in_order { s += vr.size() ... };
v1.reserve(s);
do_in_order { (details::concat_helper(v1, std::forward<A>(vr)), 0)... };
return std::move(v1); // rvo blocked
}