Home > Software design >  C : function that works with container and container of pointers as well
C : function that works with container and container of pointers as well

Time:06-24

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.

  • Related