I think I'm facing something that I imagine is a quite common problem here.
I'd like to write a function that would be able to accept both a container (let's say std::vector
) of objects, and a container of pointers to those objects.
What would be the proper way to do so?
Right now, I'm thinking
int sum(std::vector<int *> v)
{
int s = 0;
for (int * i : v) s = *i;
return s;
}
int sum(std::vector<int> v)
{
std::vector<int *> vp;
for (size_t i = 0; i < v.size(); i)
vp[i] = &v[i];
return sum(vp);
}
But it doesn't seem quite right, does it?
CodePudding user response:
Consider the standard algorithm library where the problem you see has a solution.
Most algorithms have some default behavior but often allow you to customize that behavior via functor parameters.
For your specific case the algorithm of choice is std::accumulate
.
Because this algorithm already exists I can restrict to a rather simplified illustration here:
#include <iostream>
#include <functional>
template <typename T,typename R,typename F = std::plus<>>
R sum(const std::vector<T>& v,R init,F f = std::plus<>{})
{
for (auto& e : v) init = f(init,e);
return init;
}
int main() {
std::vector<int> x{1,2,3,4};
std::vector<int*> y;
for (auto& e : x ) y.push_back(&e);
std::cout << sum(x,0) << "\n";
std::cout << sum(y,0,[](auto a, auto b) {return a *b;});
}
std::plus
is a functor that adds two values. Because the return type may differ from the vectors element type an additional template parameter R
is used. Similar to std::accumulate
this is deduced from the initial value passed as parameter. When adding int
the default std::plus<>
is fine. When adding integers pointed to by pointers, the functor can add the accumulator with the dereferenced vector element. As already mentioned this is just a simple toy example. In the above link you can find a possible implementation of std::accumulate
(which uses iterators rather than the container directly).
CodePudding user response:
With C 20 (or another ranges library), you can easily add or remove pointerness
template <std::ranges::range R, typename T>
concept range_of = requires std::same<std::ranges::range_value_t<R>, T>;
template <range_of<int *> IntPointers>
int sum_pointers(IntPointers int_pointers)
{
int result = 0;
for (int * p : int_pointers) result = *p;
return result;
}
void call_adding_pointer()
{
std::vector<int> v;
sum_pointers(v | std::ranges::views::transform([](int & i){ return &i; });
}
Or
template <range_of<int> Ints>
int sum(Ints ints)
{
int result = 0;
for (int i : ints) result = i;
return result;
}
void call_removing_pointer()
{
std::vector<int *> v;
sum(v | std::ranges::views::transform([](int * p){ return *p; });
}
CodePudding user response:
You can make a function template, which behaves differently for pointer and non-pointer:
#include <iostream>
#include <vector>
using namespace std;
template <class T>
auto sum(const std::vector<T> &vec)
{
if constexpr (std::is_pointer_v<T>)
{
typename std::remove_pointer<T>::type sum = 0;
for (const auto & value : vec) sum = *value;
return sum;
}
if constexpr (!std::is_pointer_v<T>)
{
T sum = 0;
for (const auto & value : vec) sum = value;
return sum;
}
}
int main(){
std::vector<int> a{3, 4, 5, 8, 10};
std::vector<int*> b{&a[0], &a[1], &a[2], &a[3], &a[4]};
cout << sum(a) << endl;
cout << sum(b) << endl;
}
https://godbolt.org/z/sch3KovaK
You can move almost everything out of the if constexpr
to reduce code duplication:
template <class T>
auto sum(const std::vector<T> &vec)
{
typename std::remove_pointer<T>::type sum = 0;
for (const auto & value : vec)
{
if constexpr (std::is_pointer_v<T>)
sum = *value;
if constexpr (!std::is_pointer_v<T>)
sum = value;
}
return sum;
}
https://godbolt.org/z/rvqK89sEK
CodePudding user response:
Based on @mch solution, this is what I came up to
template<typename T>
std::array<double, 3> center(const std::vector<T> & particles)
{
if (particles.empty())
return {0, 0, 0};
std::array<double, 3> cumsum = {0, 0, 0};
if constexpr (std::is_pointer_v<T>)
{
for (const auto p : particles)
{
cumsum[0] = p->getX();
cumsum[1] = p->getY();
cumsum[2] = p->getZ();
}
}
if constexpr (not std::is_pointer_v<T>)
{
for (const auto p : particles)
{
cumsum[0] = p.getX();
cumsum[1] = p.getY();
cumsum[2] = p.getZ();
}
}
double f = 1.0 / particles.size();
cumsum[0] *= f;
cumsum[1] *= f;
cumsum[2] *= f;
return cumsum;
}
@463035818_is_not_a_number: I would be definitely interested in how you would do it with a functor function.