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:
The follow-up question:
A Summation Function For Various Type Arbitrary Nested Iterable Implementation in C++
The further implementation with c++-concepts:
3 Answers 3
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 hasbegin()
andend()
- enable
Sum
for other types thandouble
(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.
-
\$\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 enableSum
for other types by addinginline int Sum(int inputNumber)
,inline unsigned int Sum(unsigned int inputNumber)
and so on separately? \$\endgroup\$JimmyHu– JimmyHu2020年10月16日 22:03:55 +00:00Commented Oct 16, 2020 at 22:03 -
1\$\begingroup\$ @JimmyHu you could call it
numbers
instead ofinputArray
. But naming is hard. For your other question, you want to use SFINAE together withenable_if
onis_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– Zeta2020年10月17日 07:51:14 +00:00Commented 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– JimmyHu2020年10月17日 13:26:48 +00:00Commented 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 additionaltypename
here, thustypename Container::value_type
. The lasttypename =
is an anonymous template parameter. I could also writetemplate <class C, class V = typename C::value_type>
. If theC::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 oneis_arithmetic
. \$\endgroup\$Zeta– Zeta2020年10月17日 17:02:57 +00:00Commented Oct 17, 2020 at 17:02
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.
-
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– Zeta2020年10月19日 19:54:55 +00:00Commented 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– nelrufus2020年10月20日 05:34:28 +00:00Commented Oct 20, 2020 at 5:34
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;
}
-
\$\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 withstd::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– JimmyHu2020年10月18日 14:26:40 +00:00Commented 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\$2020年10月18日 17:54:11 +00:00Commented Oct 18, 2020 at 17:54
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 ifT
is astd::complex<float>
? There are many types that you can sum but which don't convert tolong double
. \$\endgroup\$