Especialização parcial do modelo de função de membro e acesso a membro de dados

Eu tenho uma pergunta sobre a especialização parcial de funções de membro modeladas.

Fundo: O objetivo é calcular estatísticas descritivas de grandes conjuntos de dados grandes demais para serem mantidos na memória de uma só vez. Portanto, eu tenho classes de acumulador para a variação e a covariância, nas quais eu posso inserir os conjuntos de dados peça por peça (um valor por vez ou em partes maiores). Uma versão bastante simplificada que calcula apenas a média aritmética é

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

Uma vantagem específica desse tipo de classe de acumulador é a possibilidade de enviar valores de tipos de dados diferentes para a mesma classe de acumulador.

Problema: Isso funciona bem para todos os tipos de dados integrais. No entanto, as classes de acumuladores também devem poder manipular números complexos, calculando primeiro o valor absoluto | z | e depois empurrando-o para o acumulador. Para empurrar valores únicos, é fácil fornecer um método sobrecarregado

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

Para enviar blocos de dados por meio de iteradores, o caso não é tão simples. Para sobrecarregar corretamente, é necessária uma especialização parcial, pois precisamos conhecer o tipo de número complexo real (totalmente especializado). A maneira usual seria delegar o código real em uma estrutura interna e especializá-lo de acordo

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

Na classe acumulador, os métodos de modelo da estrutura de delegação são chamados por

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

No entanto, há um problema com essa técnica: como acessar os membros privados da classe acumulador. Como são classes diferentes, nenhum acesso direto é possível e, além disso, os métodos de push_impl precisam ser estáticos e não podem acessar os membros não estáticos do acumulador.

Posso pensar nas quatro soluções a seguir para o problema, todas com suas próprias vantagens e desvantagens:

Crie uma instância depush_impl em cada chamada paraempurrar com (possível) diminuição no desempenho devido à cópia extra.Tenha uma instância depush_impl como variável membro da classe acumulador, o que me impediria de inserir diferentes tipos de dados no acumulador, pois a instância teria que ser totalmente especializada.Tornar públicos todos os membros da classe acumuladora e passar*esta parapush_impl::empurrar() chamadas. Esta é uma solução ruim em particular devido à quebra no encapsulamento.Implemente a versão do iterador em termos da versão de valor único, ou seja, chame oempurrar() método para cada elemento com (possível) diminuição no desempenho devido à chamada de função extra.

Observe que as diminuições mencionadas de desempenho são teóricas por natureza e podem não ser um problema, devido ao alinhamento inteligente do compilador; no entanto, o realempurrar métodos podem ser muito mais complexos que o exemplo acima.

Uma solução é preferível às outras ou sinto falta de alguma coisa?

Cumprimentos e muito obrigado.

questionAnswers(3)

yourAnswerToTheQuestion