More realistic C++: how to sum two or more numbers

in #programming4 years ago

This is for C++ students a little bit more advanced than those who are the target of this post, or for C++ students who want to advance and try to leave bad practices behind.

Problem: Write a C++ program to calculate the sum of the following three integer numbers and display the result on computer screen display

When you are given such an assignment, let me know who's your teacher… And never forget: in programming it is very important to be able to abstract and generalize. The sooner you train yourself to achieve the skills, the better for your programming mind.

When the assignment says “the following three integer numbers”, you must ignore the specific numbers and think about how to write a function which is able to sum three integer numbers, no matter which one.

int sum3(int a, int b, int c) {
    return a + b + c;
}

But then, you suddenly realise that they don't need to be three: there must be generic, reusable and “scalable” way to sum N integer numbers.

The problem should really be the following:

Problem: Write a C++ program to calculate the sum of two or more integer numbers and display the result on the computer screen display

Then, let's test your solution with the following three numbers:

5609
3865
1190

In modern C++ you can write something like the following:

#include <iostream>
#include <array>
#include <numeric>

int main()
{
  auto ns = std::array<int,3>{5609, 3865, 1190};
  std::cout << std::accumulate(ns.cbegin(), ns.cend(), 0) << "\n";
  return 0;
}

Instead of array, we could use other iterable collections, e.g., vector. In this case we don't need to write down how many numbers we have:

include <iostream>
#include <vector>
#include <numeric>

int main()
{
  auto ns = std::vector<int>{5609, 3865, 1190};
  std::cout << std::accumulate(ns.cbegin(), ns.cend(), 0) << "\n";
  return 0;
}

We can wrap the accumulate in a function:

int sum(const std::vector<int>& v) {
    return std::accumulate(v.cbegin(), v.cend(), 0);
}

We use it like this:

// ...
std::cout << sum(std::vector<int>{5609, 3865, 1190}) << "\n";

But you can see that it isn't enough generic: after all, every kind of number can be summed. Why just integers as int?

Here it is when you feel that a place for template exists:

template<class T>
T sum2(const std::vector<T>& v) {
    return std::accumulate(v.cbegin(), v.cend(), (T)0);
}

Now you can use it with int, long, double…:

std::cout << sum2(std::vector<double>{5609.1, 3865.2, 1190.3}) << "\n";
std::cout << sum2(std::vector<long>{5609, 3865, 1190}) << "\n";

Maybe your teacher think you are cheating because you used accumulate. Well, let's avoid that (then we can remove the <numeric> include).

template<class T>
T sum3(const std::vector<T>& v) {
    T n = (T)0;
    for (const T e : v) {
        n += e;
    }
    return n;
}

There's also another way using modern C++ syntax, and here we are not using any collection. We are going to exploit variable arguments.

template<class T, class... Ts>
T sum4(T i, Ts... v) {
    T n = i;
    for (auto e : {v...}) {
        n += e;
    }
    return n;
}

This last one might seems a little bit odd; basically it allows you to write:

  std::cout << sum4(5609, 3865, 1190) << "\n"; 

and also the following examples:

... sum4(5)
    sum4(1, 2)
    sum4(1, 2, 3, 4, 5, 6)
    ... and so on

That is, each number you want to sum can be passed as argument. You need to pay attention to the types, though, to avoid narrowing convertions and alike. If you stick to the same type (e.g., all ints, or all doubles) you are safe.

image.png