简体   繁体   中英

How to sum all the elements of a multi-dimensional std::vector?

The idea is simple and straight-forward:
Keep breaking the n dimensional vector into n-1 dimensional constituent vectors, until you have access to the primitive-datatype objects. Then add them all.

The problem is, how to infer the return type?

It can be done like this, but it already assumes the datatype of summing variable (return-type):

typedef int SumType;

template <class T>
T Sum (const T x)
{
    return x;
}

template <class T>
SumType Sum (const std::vector<T>& v)
{
    SumType sum = 0;
    for (const auto& x: v)
        sum += Sum(x);
    return sum;
}

But I don't want to do it like above. I feel like its against the spirit of meta-programming.

We must infer the return-type by keeping dissecting the vector into its constituent vectors until we reach the primitive-datatype objects, and then choose the return-type as the primitive-datatype.

Is it possible in C++ ? (I'm noob in meta-programming)


PS
std::accumulate() from <numeric> could have been helpful, but it by-passes the problem by inferring the return-type from its third argument __init .

What we can do is use TC 's data_type class to get the the underlying type. That is defined as

 template<class T> struct voider { typedef void type; }; template<class T, class = void> struct data_type { typedef T type; }; template<class T> struct data_type<T, typename voider<typename T::value_type>::type> : data_type<typename T::value_type> {}; 

Using that we can modify the primary Sum to

template <class T, class Ret = typename data_type<std::vector<T>>::type>
Ret Sum (const std::vector<T>& v)
{
    Ret sum = 0;
    for (const auto& x: v)
        sum += Sum(x);
    return sum;
}

So then you can use something like

int main()
{
    std::cout << Sum(std::vector<std::vector<std::vector<int>>>{{{1},{2},{3}},{{4},{5},{6}}});
}

which outputs

21

Live Example

This can be done without any template meta programming. You can let the compiler infer the type using auto and decltype :

template <class T>
T Sum(const T x) {
    return x;
}

template <class T>
auto Sum(const std::vector<T> &v) {
    decltype(Sum(v[0])) sum = 0;
    for (const auto &x : v)
        sum += Sum(x);
    return sum;
}

The return type of Sum is automatically deduced from sum and the type of sum is whatever Sum(v[0]) returns. Eventually you will end up with the first version of Sum which returns T and the compiler knows that type.

Demo

You've almost figured out the answer for yourself. Pay attention to this line:

sum += Sum(x);

The type of sum , which is what we're after, must be something compatible for assignment with the result of our recursive call to Sum . One such type, given your requirements, is certainly the result type of the call.

We don't have to rely on just a fuzzy feeling though. Meta-programming is, after all, programming. You may not have realised it, but your problem is one of well-founded recursion which means that the principle of induction can guide us towards an answer.

  • in the base case, we have a numerical, non-vector element_type element; , meaning our result type is… element_type . you've in fact already managed this step, it's the first overload:

     template<typename T> T Sum(T element); 
  • in the recursive case we have:

    • std::vector<element_type> vec;
    • the induction hypothesis, ie:

       // given element_type element; // we know the following is well-formed and a numerical type using recursive_result_type = decltype( Sum(element) ); 

      Since the vector elements have type element_type , the induction hypothesis gives us that the result of calling Sum on them has all the properties we want. (The justification for our += intuition is rooted here.) We have our anser: we use recursive_result_type as-is.

Now as it turns out that second overload cannot just be written eg like so:

// doesn't behave as expected
template<typename Element>
auto Sum(std::vector<Element> const& vec) -> decltype( Sum(vec.front()) );

The reason being that the current Sum overload being declared is not in scope in the return type (even though it is in the definition body). One way to work around that is to rely on class scope, which is more accommodating:

// defining a functor type with operator() overloads
// would work just as well
struct SumImpl {
    template<typename Element>
    static T apply(Element element)
    { return element; }

    template<typename Element>
    static auto apply(std::vector<Element> const& vec)
    -> decltype( apply(vec.front()) )
    {
        using result_type = decltype( apply(vec.front()) );
        result_type sum = 0;
        for(auto const& element: vec) {
            sum += apply(element);
         }
         return sum;
    }
};

template<typename Arg>
using sum_result_t = decltype( SumImpl::apply(std::declval<Arg const&>()) );

template<typename Arg>
sum_result_t<Arg> Sum(Arg const& arg)
{ return SumImpl::apply(arg); }

Coliru demo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM