Especialización parcial de plantilla de función miembro y acceso a miembro de datos

Tengo una pregunta sobre la especialización parcial de las funciones miembro con plantilla.

Antecedentes: El objetivo es calcular estadísticas descriptivas de grandes conjuntos de datos que son demasiado grandes para guardarlos en la memoria a la vez. Por lo tanto, tengo clases de acumulador para la varianza y la covarianza donde puedo insertar los conjuntos de datos pieza por pieza (ya sea un valor a la vez o en fragmentos más grandes). Una versión bastante simplificada que computa solo la media aritmética es

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;
        }
    }
};

Una ventaja particular de este tipo de clase de acumulador es la posibilidad de insertar valores de diferentes tipos de datos en la misma clase de acumulador.

Problema: Esto funciona bien para todos los tipos de datos integrales. Sin embargo, las clases de acumuladores también deberían poder manejar números complejos calculando primero el valor absoluto | z | y luego empujándolo hacia el acumulador. Para empujar valores individuales es fácil proporcionar un método sobrecargado

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));
}

Sin embargo, para enviar fragmentos de datos a través de iteradores, el caso no es tan simple. Para sobrecargar correctamente se requiere una especialización parcial ya que necesitamos conocer el tipo de número complejo real (totalmente especializado). La forma habitual sería delegar el código real en una estructura interna y especializarlo en consecuencia

// 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;
        }
    }
};

En la clase de acumulador, los métodos con plantilla de la estructura de delegación son llamados por

template <class InputIt>
void push(InputIt first, InputIt last)
{
    push_impl<InputIt, typename std::iterator_traits<InputIt>::value_type>::push(first, last);
}

Sin embargo, hay un problema con esta técnica que es cómo acceder a los miembros privados de la clase de acumulador. Como son clases diferentes, no es posible el acceso directo y, además, los métodos de push_impl deben ser estáticos y no pueden acceder a los miembros no estáticos del acumulador.

Puedo pensar en las siguientes cuatro soluciones al problema que tienen sus propias ventajas y desventajas:

Crear una instancia depush_impl en cada llamada aempujar con (posible) disminución en el rendimiento debido a la copia adicional.Tener una instancia depush_impl como variable miembro de la clase de acumulador, lo que me impediría insertar diferentes tipos de datos en el acumulador ya que la instancia tendría que ser completamente especializada.Hacer públicos todos los miembros de la clase de acumulador y aprobar*esta apush_impl:: ::empujar() llamadas. Esta es una mala solución particular debido a la interrupción en la encapsulación.Implemente la versión del iterador en términos de la versión de valor único, es decir, llame alempujar() Método para cada elemento con (posible) disminución en el rendimiento debido a la llamada de función adicional.

Tenga en cuenta que las disminuciones de rendimiento mencionadas son teóricas en su naturaleza y podrían no ser un problema en absoluto debido a la inteligente alineación del compilador, sin embargoempujar Los métodos pueden ser mucho más complejos que el ejemplo anterior.

¿Es una solución preferible a las otras o me pierdo algo?

Un saludo y muchas gracias.

Respuestas a la pregunta(3)

Su respuesta a la pregunta