Just for sake of example, let's say I have a function that computes the average value of a given std::vector<T>
:
template<class T>
T vec_average(const std::vector<T>& vec) {
assert(!vec.empty());
T accumulator = vec[0] - vec[0]; // Init to 0.
for (const auto& el : vec) { accumulator += el; }
return accumulator / double(vec.size());
}
Now of course the requirement is that all the operators (+=, /, -) are defined for T
and there might be rounding issues, but the more severe problem is that calling vec_average
with a "small type" like uint8_t
will quickly lead to bad results due to overflow.
type_traits
to the rescue! you might say and indeed that is my initial attempt at solving this:
template<class T>
struct safe_accum {
typedef typename std::conditional<std::is_integral<T>::value, int64_t, T>::type type;
};
template<class T>
T vec_average(const std::vector<T>& vec) {
assert(!vec.empty());
safe_accum<T>::type accumulator = vec[0] - vec[0]; // Init to 0.
for (const auto& el : vec) { accumulator += el; }
return accumulator / double(vec.size());
}
But: This only solves it for all the integer and floating point types. As soon as I have a struct or class type this breaks down (because std::is_integral
doesn't work on class types):
struct Vector3uc {
uint8_t data[3];
// Operator definitions etc...
};
void foobar() {
std::vector<Vector3uc> bla;
// ...
Vector3uc avg = vec_average(bla); // Won't work!
}
Therefore, my question is: Can I have a safe_accum
type that works for "classy" input types T
as well?
For example I'd like to tell the compiler that a safe_accum<Vector3uc>::type
would have to result in a Vector3ll
(a 3-vector of int64_t), whereas a safe_accum<Vector3f>::type
would simply be a Vector3f
.
I'm pretty sure it can be done with typedefs and/or template specialization but this is a bit of a grey area in my knowledge of C++...
PS: Just to be clear I wouldn't mind defining these translations (Vector3uc
--> Vector3ll
) manually in the code for each relevant type, as long as it's only a line or two per type.