2

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)


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

AneesAhmed777
  • 2,475
  • 2
  • 13
  • 18
  • SumType must be inferable at compile time, just like with Templates so the compiler knows which function prototypes to generate. You can look at `decltype` to define the return value of SumType from the base data. For a more flexible solution, look at wrapping the base data into a variant class like in scripting languages. – learnvst May 17 '17 at 12:17
  • You probably want `SumType sum {}`. There's no apparent reason to assume numeric 0 can be converted to `SumType`. – MSalters May 17 '17 at 13:23

3 Answers3

2

What we can do is use T.C.'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

Community
  • 1
  • 1
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
2

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

nwp
  • 9,623
  • 5
  • 38
  • 68
1

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, i.e.:

      // 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 e.g. 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

Luc Danton
  • 34,649
  • 6
  • 70
  • 114