Computing the neutral element
For those cases with a unique neutral element (wrt. addition)
we can offer a simplification
which does not need init
any more.
To implement this, we need to deduce,
in addition to the value type,
also the value of the neutral element from Iterator
.
In analogy to value_type
we can define a type mapping neutral_element_addition
,
which now does not map type to type but type to value.
It is also more natural to define this map directly on the value type instead of the iterator type.
As normally, the neutral element can be initialized with the literal 0,
we use this as default definition in the primary template.
template<typename T>
struct neutral_element_addition
{ static T value() { return T(0); } };
template<>
struct neutral_element_addition<std::string>
{ static std::string value() { return std::string(""); } };
In the example above, only for std::string
an exception (via template specialization) was defined.
For your own, blazingly fast 3D vector class vec3
you have to implement a similar specialization, if vec3(0)
does not work:
template<>
struct neutral_element_addition<vec3>
{ static vec3 value() { return vec3(0.0,0.0,0.0); } };
Sum with automatic init
In the new implementation of sum2
we now get the neutral element via
neutral_element_addition
, spending a typedef
for clarity:
template<typename Iterator>
typename value_type<Iterator>::result
sum2b(Iterator a, Iterator a_end)
{
typedef typename value_type<Iterator>::result value_t;
value_t res = neutral_element_addition<value_t>::value();
for(; a != a_end; ++a)
res += *a;
return res;
}
We can offer this comfortable overload alongside the
general version
with explicit init.
So, finally our examples for string
and float
work without any tweaks at the calling site:
float a[] = { 0.0, 0.5, 1.0, 1.5 };
float suma = sum2b(a, a+4); // beware of long sequences!
std::string words[] = { "This", "is", "a", "sentence" };
std::string sentence = sum2b(words, words+4);
Narrow value types hit again
... just the output for strings is not as pleasant. And an old problem is striking back: Automatic computation of the init argument makes it more likely to fall into the trap of a too narrow value type:
vector<unsigned char> dice_rolls(BigN);
// surprise: unsigned char used for summing
unsigned sum_dice_rolls = sum2b(dice_rolls.begin(), dice_rolls.end());
When only the version (sum3
)
with explicit init
parameter is available,
there is at least a visible, explicit mention of that inadequate type
at the calling site (and a chance of the programmer choosing a better type for init
).
It is a good excercise to think about if and how this could be resolved
(and you'll leave the scope of this tutorial when doing so).