6
\$\begingroup\$

This is the follow-up question for A Summation Function For Arbitrary Nested Vector Implementation in C++. The following code is the improved version based on Zeta's answer. I am trying to enhance this the sum function which can deal with iterable things which has begin() and end() (such as std::array) of various types (such as int, char or unsigned int) number. I am not familiar with the usage of std::enable_if and std::is_arithmetic for checking type constraint in template. If there is any possible improvement, please let me know.

template<class Container, typename = typename Container::value_type>
inline long double Sum(const Container& numbers)
{
    long double sumResult = 0.0;
    for (auto& element : numbers)
    {
        sumResult += Sum(element);
    }
    return sumResult;
}

template<class T,
    std::enable_if_t<std::is_arithmetic<T>::value, int> = 0,
    std::enable_if_t<std::is_arithmetic<T>::value, double> = 0>
T Sum(T inputNumber)
{
    return inputNumber;
}

Some tests for this sum function:

int testNumber = 1;

std::vector<decltype(testNumber)> testVector1;
testVector1.push_back(testNumber);
testVector1.push_back(testNumber);
testVector1.push_back(testNumber);
std::cout << std::to_string(Sum(testVector1)) + "\n";

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

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

//  std::array test case
std::array<long double, 90> numberArray;
for (size_t i = 0; i < 90; i++)
{
    numberArray[i] = 1;
}
std::cout << std::to_string(Sum(numberArray)) + "\n";

The summary information:

  • Which question it is a follow-up to?

    A Summation Function For Arbitrary Nested Vector Implementation in C++

  • What changes has been made in the code since last question?

    • The previous question focus on single type long double and the goal in this question is trying to deal with various type number.

    • The previous question focus on std::vector and the other iterable things which has begin() and end() are considered here.

    • The variable name has been modified in order to make better understanding.

  • Why a new review is being asked for?

    Is it a good idea to set the type constraint like std::enable_if_t<std::is_arithmetic<T>::value, int> = 0, std::enable_if_t<std::is_arithmetic<T>::value, double> = 0>?

Oct 18, 2020 Update

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Proper constraints

Is it a good idea to set the type constraint like std::enable_if_t<std::is_arithmetic<T>::value, int> = 0, std::enable_if_t<std::is_arithmetic<T>::value, double> = 0>?

It is not a good idea to constrain the value type to the arithmetic ones. It will only match primitive integer and floating point types. There are many more types that you can sum together, either ones from the STL (like std::complex) or custom types.

Also, the constraint on the overload that sums over containers is incorrect. You only restrict it to classes that have a value_type, but that doesn't necessarily mean it is a container. Again, std::complex is an example of a class that has a value_type but isn't a container. Instead, you want to restrict it to something that has the properties of a container that you are actually using: that it has a begin() and end() for example. See this question for some ideas of how to check for a container type.

Return type

If you pass a container to Sum(), the result is always long double. But what if I want to sum integers, or std::complex numbers, or something completely different like std::strings? You want the return type to match the value_type of the inner-most container. Have a look at how std::accumulate() handles this.

Summing non-container types

Normally I'm all for handling corner cases properly, and the case of summing over something that has zero nested containers, but just a single value, can be considered such a corner case. But in this case, I think it will most likely be a programming error if you tried to sum a single value. So consider whether it makes sense to have an overload that handles the sum of a single value.

Redundant calls to std::to_string()

Not related to Sum(), but in your example you are converting the results to a string with std::to_string(), but the immediately writing it to std::cout. This is unnecessary, std::cout already knows how to format a long double.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for answering. About the proper constraints part, I realize that there are many more types that can be summed together. Is there any hint or example for dealing with this part? \$\endgroup\$
    – JimmyHu
    Commented Oct 18, 2020 at 10:27
  • 2
    \$\begingroup\$ Yes: I would just not put any constraints on the value type. If it can't be summed you'll get a compile error. With C++20's concepts, you could create concept Summable that checks whether the value types can be summed, and give a helpful error message if not. You could do something similar with SFINAE, although that's more ugly, and without a clean error message it won't be as useful. See for an example this question. \$\endgroup\$
    – G. Sliepen
    Commented Oct 18, 2020 at 11:21

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.