9
\$\begingroup\$

I am trying to deal with some calculations on nested vector data in C++. The nested vector data may be like std::vector<long double>, std::vector<std::vector<long double>>, or std::vector<std::vector<std::vector<long double>>>. I want to focus on summation here, and the calculation of summation could be done with the Sum function implemented here. Is there any possible improvement of this code?

The function declaration part is as below.

template <class T>
static long double Sum(const std::vector<T> inputArray);
static long double Sum(long double inputNumber);

The function implementation part is as below.

template<class T>
inline long double Sum(const std::vector<T> inputArray)
{
    long double sumResult = 0.0;
    for (auto& element : inputArray)
    {
        sumResult += Sum(element);
    }
    return sumResult;
}

inline long double Sum(long double inputNumber)
{
    return inputNumber;
}

Test for this sum function:

std::vector<long double> testVector1;
testVector1.push_back(1);
testVector1.push_back(1);
testVector1.push_back(1);
std::cout << std::to_string(Sum(testVector1)) + "\n";

std::vector<std::vector<long double>> testVector2;
testVector2.push_back(testVector1);
testVector2.push_back(testVector1);
testVector2.push_back(testVector1);
std::cout << std::to_string(Sum(testVector2)) + "\n";

std::vector<std::vector<std::vector<long double>>> testVector3;
testVector3.push_back(testVector2);
testVector3.push_back(testVector2);
testVector3.push_back(testVector2);
std::cout << std::to_string(Sum(testVector3)) + "\n";

Oct 18, 2020 Update

Reference:

\$\endgroup\$
2
  • 1
    \$\begingroup\$ How is this template meta programming? please remove the tag \$\endgroup\$
    – user228914
    Commented Oct 16, 2020 at 10:03
  • 2
    \$\begingroup\$ long double can be very inefficient on x86 and x86_64, since it will use the 80-bit floating point format, which doesn't fit in SSE registers and thus the compiler then cannot use SSE instructions. Also, what if T is a std::complex<float>? There are many types that you can sum but which don't convert to long double. \$\endgroup\$
    – G. Sliepen
    Commented Oct 16, 2020 at 13:06

3 Answers 3

6
\$\begingroup\$

The template seems reasonable, although inputArray is a misnomer. However, there is a major drawback that can be remedied with a single &: use call-by-reference instead of call-by-value (see guidelines):

template<class T>
inline long double Sum(const std::vector<T> &inputArray)
{ 
    ...
}

Other than that it's perfectly fine for summing arbitrary nested std::vector<double>.

That being said, there is some room for further experiments:

  • enable Sum for anything that has begin() and end()
  • enable Sum for other types than double (e.g. int)

Also, I'm a little bit concerned by the comment that declaration and definition were split. While it's possible, it's usually not intended.

\$\endgroup\$
4
  • \$\begingroup\$ Thank you for answering. Is there any better suggestion about the naming to the input parameter of the template function Sum here? On the other hand, is it a good idea to enable Sum for other types by adding inline int Sum(int inputNumber), inline unsigned int Sum(unsigned int inputNumber) and so on separately? \$\endgroup\$
    – JimmyHu
    Commented Oct 16, 2020 at 22:03
  • 1
    \$\begingroup\$ @JimmyHu you could call it numbers instead of inputArray. But naming is hard. For your other question, you want to use SFINAE together with enable_if on is_arithmetic. A (poor) proof of concept can be found here: ideone.com/PLVF9E. However, I'm not that familiar with SFINAE. You probably want to tinker on that IDEONE code a little bit more and ask for another review if you want to use it in more serious projects or in production. (Especially since I didn't rename the input...🤦) \$\endgroup\$
    – Zeta
    Commented Oct 17, 2020 at 7:51
  • \$\begingroup\$ Thank you for providing the useful comments. I'm trying to figure out how's template<class Container, typename = typename Container::value_type> (in your example) work. I am not familiar with this usage. If there is any useful information please let me know. \$\endgroup\$
    – JimmyHu
    Commented Oct 17, 2020 at 13:26
  • 1
    \$\begingroup\$ @JimmyHu that's SFINAE, as well as an anonymous second template parameter. Container::value_type "only" works on containers. value_type is a type, but the compiler needs an additional typename here, thus typename Container::value_type. The last typename = is an anonymous template parameter. I could also write template <class C, class V = typename C::value_type>. If the C::value_type substitution fails, we don't get a compiler error (that's the SFINAE part), but instead another template is considered, in this case the one is_arithmetic. \$\endgroup\$
    – Zeta
    Commented Oct 17, 2020 at 17:02
2
\$\begingroup\$

As @Zeta pointed out, to enable nested sum for other scalar types, a possible implementation of Sum could be:

#include <iostream>
#include <vector>

template <typename T>
inline void Sum(const T &inputArrayElement, T &runningSum) {
  runningSum += inputArrayElement;
}

template <typename T, typename U>
inline void Sum(const T &inputArray, U &runningSum) {
  for (const auto &element : inputArray) {
    Sum(element, runningSum);
  }
}

Test example

std::vector<std::vector<std::vector<double>>> v = {{{1.0, 3.0}, {2.0}},
                                                   {{2.0}, {3.0}}};

double sum = 0.0;
Sum(v, sum);

std::cout << sum << std::endl;

There might be other ways of doing this using type comparison as discussed here.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ As Mast said on the other answer: You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. This will also prevent downvotes. \$\endgroup\$
    – Zeta
    Commented Oct 19, 2020 at 19:54
  • \$\begingroup\$ @Zeta. The only improvement I had thought about was using const& instead of const (which you had already pointed out in your answer). My solution was an example of templatizing the scalar type. I didn't give details of scalar type deduction since it was discussed in a more general manner in the stackoverflow link I had included. \$\endgroup\$
    – nelrufus
    Commented Oct 20, 2020 at 5:34
0
\$\begingroup\$

I'd suggest a more generic approach:

template<typename T, typename = void>
struct is_container : std::false_type {};

template<typename T>
struct is_container<T,
                    std::void_t<decltype(std::declval<T>().begin()),
                                decltype(std::declval<T>().end()),
                                typename T::value_type
                    >> : std::true_type {
};

// empty
constexpr long double Sum() {
  return 0.0;
}

// a number (arithmetic)
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value, T>::type* = nullptr>
constexpr long double Sum(const T& item) {
  return item;
}

// container
template<typename Container,
    typename std::enable_if<is_container<Container>::value, Container>::type* = nullptr>
constexpr long double Sum(const Container& container) {
  return std::accumulate(container.begin(), container.end(), Sum(), [](auto&& sum, const auto& item) {
    return sum + Sum(item);
  });
}

// tuple
template<typename...Args>
constexpr long double Sum(const std::tuple<Args...>& tuple) {
  return std::apply([](const auto& ... values) {
    return (Sum(values) + ...);
  }, tuple);
}

// 2 or more args
template<typename T1, typename T2, typename ... Args>
constexpr long double Sum(const T1& item1, const T2& item2, const Args& ...args) {
  return Sum(item1) + Sum(item2) + (Sum(args) + ...);
}

Then you can do something like this:

int main() {
  std::array a{ 0.1, 0.2, 0.3 };
  std::vector v{ 0.4, 0.5, 0.6 };
  std::list l{ 0.7, 0.8, 0.9 };
  std::vector vv{
      std::vector{ 0.0, 0.1, 0.2 },
      std::vector{ 1.0, 2.1, 2.2 },
      std::vector{ 2.0, 2.1, 2.2 },
  };
  std::vector vvv{ std::vector{ std::vector{ 3.0, 3.1, 3.2 }}};
  std::tuple t{ .1, 42, unsigned(1), 'c', std::vector{ 4.0, 4.1, 4.2, 4.3 }};
  std::cout << Sum(.1, 42, l, a, v, vv, vvv, t) << "\n";
  return 0;
}
\$\endgroup\$
2
  • \$\begingroup\$ Thank you for your answer. The example of usage you provided in main is impressive. However, it seems to be still hard to deal with std::complex. I am trying to dig into new solution with C++20's concepts. The further info is at codereview.stackexchange.com/a/250792/231235 \$\endgroup\$
    – JimmyHu
    Commented Oct 18, 2020 at 14:26
  • 3
    \$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. \$\endgroup\$
    – Mast
    Commented Oct 18, 2020 at 17:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.