How to execute mathematical operations between two boost::multi_arrays (C++)? - c++

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;
}

Related

Concept checking using ranges-v3 library

In this blog post, I saw Eric Niebler explaining his take on concept checking. It looked like a nice balance between simplicity and practicality for me so I wanted to give it a try.
To test things myself, I came up with a quick concept of vector spaces. Here is the code:
#include <range/v3/utility/concepts.hpp>
#include <array>
#include <algorithm>
#include <string>
struct vector_space
{
// Valid expressions
template<typename Field, typename Vector>
auto requires(Field &&f, Vector &&v1, Vector &&v2) -> decltype(
ranges::concepts::valid_expr(
f * v1,
v1 + v2
));
};
template<typename Field, typename Vector>
constexpr bool VectorSpace()
{
return ranges::concepts::models<vector_space, Field, Vector>();
}
template<typename Field, typename Vector,
CONCEPT_REQUIRES_(VectorSpace<Field, Vector>())>
void linear_comb(Field f1, Vector v1, Field f2, Vector v2)
{
return (f1 * v1) + (f2 * v2);
}
template <typename T, std::size_t dim>
std::array<T, dim> operator+(std::array<T, dim> const& a1,
std::array<T, dim> const& a2) {
std::array<T, dim> res;
std::transform(a1.begin(), a1.end(), a2.begin(),
res.begin(),
[](T const& e1, T const& e2) {
return e1 + e2;
});
return res;
}
template <typename Field, typename T, std::size_t dim>
std::array<T, dim> operator*(std::array<T, dim> const& a,
Field f) {
std::array<T, dim> res;
std::transform(a.begin(), a.end(),
res.begin(),
[f](T const& e) {
return f * e;
});
return res;
}
template <typename Field, typename T, std::size_t dim>
std::array<T, dim> operator*(Field f, std::array<T, dim> const& a) {
return a * f;
}
int main() {
std::string s1 = "hello";
std::string s2 = "world";
std::array<float, 4> a1{1.1f, 2.2f, 3.3f, 4.4f};
std::array<float, 4> a2{4.4f, 3.3f, 2.2f, 1.1f};
std::array<float, 4> a3 = (3.14f * a1) + (2.71f * a2);
linear_comb(3.14f, a1, 2.71f, a2);
}
As you can see, I want to check that given v1, v2 of Vector and f of Field, expressions f * v1 and v1 + v2 to make sense. In short, I want scalar multiplication and vector addition. Although I can correctly compute a3 in main, function linear_comb tells me that concept VectorSpace<Field, Vector> is not satisfied. It correctly deduces Field as float and Vector as std::array<float, 4>. Why is it not seeing above as valid expressions then?

Concatenating a sequence of std::arrays

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 << " ";
}
}

Convert variadic template to constexpr

I'm wondering if it's possible to convert simple loop that is invoked through a parameter pack into a constexpr with simpler code. This example code demonstrates what I'm trying to do
struct Student {
AgeCategory age;
Income income;
bool is_student;
CreditRating credit_rating;
bool buys_computer;
};
auto find_probability(const double x, const double mean, const double stdev) -> double;
typedef std::tuple<double, double> MeanStdDev;
typedef std::vector<MeanStdDev> MeanStdDevVec;
// This code seems verbose to me. Is there a simpler way to express this
// loop which iterates over a vector and parameter pack, possibly
// using constexpr. C++14/17 idioms are fine.
template<typename Attr>
auto get_probability(const MeanStdDevVec& v, const size_t n, const Student& s, Attr attr) -> double {
double mean, stdev;
std::tie(mean, stdev) = v[n];
return find_probability(static_cast<double>(std::invoke(attr, s)), mean, stdev);
}
template<typename First, typename... Attr>
auto get_probability(const MeanStdDevVec& v, const size_t n, const Student& s, First f, Attr... attr) -> double {
double mean, stdev;
std::tie(mean, stdev) = v[n];
return find_probability(static_cast<double>(std::invoke(f,s)), mean, stdev) * get_probability(v, n + 1, s, attr...);
}
template<typename ...Attr>
auto calculate_class_probability(const std::map<bool, MeanStdDevVec>& summaries, const Student& s, Attr... attributes) {
for (const auto& i : summaries) {
get_probability(i.second, 0L, s, attributes...);
}
}
called from
Student s;
calculate_class_probability(class_summaries, s , &Student::age, &Student::income, &Student::credit_rating, &Student::is_student);
It doesn't necessarily make the code shorter as a whole, but it does separate out a generic part that you can reuse easily, and IMHO makes the code clearer. The key in this particular case is a function that maps packs into arrays of a certain type:
template <class T, class F, class ... Args>
std::array<T, sizeof...(Args)> pack_to_array(F f, Args&& ... args) {
return {(f(std::forward<Args>(args)))...};
}
In your case, this isn't quite enough, as you want to zip it with a vector. So a useful modification of this, would be to make the integer index of the pack element available and pass it to the function:
template <class T, class F, class ... Args>
std::array<T, sizeof...(Args)> index_pack_to_array(F f, Args&& ... args) {
std::size_t i = 0;
return {(f(i++, std::forward<Args>(args)))...};
}
Now, you can use this function like so:
template<typename... Attr>
double get_probability(const MeanStdDevVec& v, const Student& s, Attr... attr) {
assert(v.size() == sizeof...(Attr));
auto probs = index_pack_to_array<double>(
[&] (std::size_t i, auto&& a) -> double {
return // ... (get probability from v[i], s, and a)
},
std::forward<Attr>(attr)...);
return std::accumulate(probs.begin(), probs.end(), 1.0,
[] (double p1, double p2) { return p1 * p2; });
}
Not sure to understand what do you want, but I suppose you can throw away your getProbability() and rewrite calculate_class_probability() as follows
template <typename ... Attr>
auto calculate_class_probability
(std::map<bool, MeanStdDevVec> const & summaries,
Student const & s, Attr ... attributes)
{
using unusedT = int[];
for ( const auto & i : summaries )
{
double mean, stdev;
double prob {1.0};
std::size_t n {0};
(void)unusedT { (std::tie(mean, stdev) = i.second[n++],
prob *= find_probability(static_cast<double>(std::invoke(attributes,s)), mean, stdev),
0)... };
// in prob the calculate value
}
}
But: no: I don't think it's possible to write this as constexpr; the operator[] for std::vector<> isn't constexpr

Implementing access to vector of stucts using a vector as indices

Some higher-level languages have this feature, and I'm attempting to implement in C++. I'm not sure if there's a library that already does this somewhere, but if there is, it would save me a lot of trouble. Anyway, here's what I'm trying to accomplish:
Let's say I have a vector of structs that have a double and an int as members, and that I have another vector of ints that represents the indices of the elements in the vector of structs that I want to keep.
typedef struct
{
double x;
int y;
}s;
std::vector<s> v;
std::vector<int> inds;
Is there a way to implement accessing the elements in the structure using the vector of indices in a manner similar to this, or has it been implemented elsewhere?
std::vector<double> dResult = v[inds].x;
std::vector<int> iResult = v[inds].y;
It would also be nice to be able to access all of the elements in this manner:
std::vector<double> d = v.x;
Are these things possible?
You cannot use that syntax with existing definitions of std::vector.
You cannot create a global operator overload function that provides that syntax since operator[]() can be overloaded only as a member function.
You can create a non-member function that provides the functionality but without using that syntax.
template <typename T1, typename T2>
std::vector<T2> getElements(std::vector<T1> const& vec,
std::vector<int> const& indices,
T2 (T1::*member))
{
std::vector<T2> ret;
for ( auto index : indices )
{
ret.push_back(vec[index].*member);
}
return ret;
}
and then use it as:
std::vector<s> v;
std::vector<int> inds;
std::vector<double> dResult = getElements(v, inds, &s::x);
No such built-in functionality exists, and I'm not aware of any existing library solutions either. But it's not too difficult to write a couple of function templates that do this for you.
template<typename Container, typename T, typename M>
std::vector<M> select_mem(Container const& c, M T::* mem)
{
std::vector<M> result;
result.reserve(c.size());
std::transform(c.begin(), c.end(), std::back_inserter(result),
std::mem_fn(mem));
return result;
}
The above template takes a reference to a container and a pointer to a data member. It then copies that data member from each element in the input container into the output vector.
template<typename Container, typename Indices, typename T, typename M>
std::vector<M> select_mem(Container const& c, Indices const& ind, M T::* mem)
{
std::vector<M> result;
result.reserve(ind.size());
std::transform(ind.begin(), ind.end(), std::back_inserter(result),
[&c, mem](typename Indices::value_type const& i) {
return std::mem_fn(mem)(c[i]);
});
return result;
}
This is an extension of the previous template that also accepts a container of indices, and only copies the data members at the indicated indices within the input container.
Use them as follows:
std::vector<s> v {{10, 1}, {20, 2}, {30, 3}, {40, 4}};
for(auto const& x : select_mem(v, &s::x)) {
std::cout << x << ' ';
}
std::cout << '\n';
std::vector<int> indices{1,2};
for(auto const& x : select_mem(v, indices, &s::x)) {
std::cout << x << ' ';
}
std::cout << '\n';
Live demo
To get syntax close to what you desired you could create a class that wraps a vector of your structs and has member functions for each of the member variables in your struct:
class VecS {
const std::vector<s>& v;
const std::vector<int>* inds;
template<typename R>
std::vector<R> impl(R s::* pm) const {
if (inds) {
std::vector<R> ret(inds->size());
auto get_at_index = [this, pm](int index){ return v[index].*pm; };
std::transform(inds->begin(), inds->end(), ret.begin(), get_at_index);
return ret;
}
std::vector<R> ret(v.size());
std::transform(v.begin(), v.end(), ret.begin(), std::mem_fn(pm));
return ret;
}
public:
VecS(const std::vector<s>& v) : v(v), inds(nullptr) {}
VecS(const std::vector<s>& v, const std::vector<int>& inds) : v(v), inds(&inds) {}
std::vector<double> x() const { return impl(&s::x); }
std::vector<int> y() const { return impl(&s::y); }
};
If you are willing to abuse operator[] you can go one step further and add something like:
VecS operator[](const std::vector<int>& inds) { return VecS(v, inds); }
And then you can write:
auto vs = VecS(v);
auto dResult = vs[inds].x();
auto iResult = vs[inds].y();
and of course:
auto d = vs.x();
Live demo.

variadic template function to concatenate std::vector containers

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
}