I have a question regarding partial specialisation of templated member functions.
Background: The goal is to compute descriptive statistics of large datasets which are too large to be hold in memory at once. Therefore I have accumulator classes for the variance and the covariance where I can push in the datasets piece by piece (either one value at a time or in larger chunks). A rather simplified version computing the arithmetic mean only is
class Mean
{
private:
std::size_t _size;
double _mean;
public:
Mean() : _size(0), _mean(0)
{
}
double mean() const
{
return _mean;
}
template <class T> void push(const T value)
{
_mean += (value - _mean) / ++_size;
}
template <class InputIt> void push(InputIt first, InputIt last)
{
for (; first != last; ++first)
{
_mean += (*first - _mean) / ++_size;
}
}
};
One particular advantage of this kind of accumulator class is the possibility to push values of different datatypes into the same accumulator class.
Problem: This works fine for all integral datatypes. However the accumulator classes should be able to handle complex numbers as well by first calculating the absolute value |z| and then pushing it to the accumulator. For pushing single values it's easy to provide an overloaded method
template <class T> void push(const std::complex<T> z)
{
T a = std::real(z);
T b = std::imag(z);
push(std::sqrt(a * a + b * b));
}
For pushing chunks of data via iterators however the case not quite as simple. In order to overload correctly a partial specialisation is required since we need to know the actual (fully specialised) complex number type. The usual way would be to delegate the actual code in an internal struct and specialise it accordingly
// default version for all integral types
template <class InputIt, class T>
struct push_impl
{
static void push(InputIt first, InputIt last)
{
for (; first != last; ++first)
{
_mean += (*first - _mean) / ++_size;
}
}
};
// specialised version for complex numbers of any type
template <class InputIt, class T>
struct push_impl<InputIt, std::complex<T>>
{
static void push(InputIt first, InputIt last)
{
for (; first != last; ++first)
{
T a = std::real(*first);
T b = std::imag(*first);
_mean += (std::sqrt(a * a + b * b) - _mean) / ++_size;
}
}
};
In the accumulator class the templated methods of the delegation struct are then called by
template <class InputIt>
void push(InputIt first, InputIt last)
{
push_impl<InputIt, typename std::iterator_traits<InputIt>::value_type>::push(first, last);
}
However there is one problem with this technique which is how to access the private members of the accumulator class. Since they are different classes no direct access is possible and furthermore the methods of push_impl need to be static and cannot access the non-static members of the accumulator.
I can think of the following four solutions to the problem which all have their own advantages and disadvantages:
- Create an instance of push_impl in each call to push with (possible) decrease in performance due to the extra copy.
- Have an instance of push_impl as member variable of the accumulator class, which would prevent me from pushing different datatypes into the accumulator since the instance would have to be fully specialised.
- Make all members of the accumulator class public and pass *this to push_impl::push() calls. This is a particular bad solution due to the break in encapsulation.
- Implement the iterator version in terms of the single value version, i.e. call the push() method for each element with (possible) decrease in performance due to the extra function call.
Note that the mentioned decreases performance are theoretical in their nature and might be no problem at all due to clever inlining by the compiler, however the actual push methods might be much more complex than the example from above.
Is one solution preferable to the others or do I miss something?
Best regards and many thanks.
As commented, you don't need to use partial specialization for this at all, indeed partial specialization is usually pretty easy to avoid, and preferred to avoid.
Since
push_impl
is a (private) member function you don't need to do anything special any more.Compared to your proposed solutions, this has no extra performance cost. It's the same number of function calls, the only difference is passing a stateless type by value, which is a wholly trivial optimization for the compiler. And there's no sacrifice in encapsulation either. And slightly less boilerplate.