部分成员函数模板特化和数据成员访问

Partial member function template specialisation and data member access

我对模板化成员函数的偏特化有疑问。

背景: 目标是计算大型数据集的描述性统计数据,这些数据集太大而无法一次保存在内存中。因此,我有方差和协方差的累加器 classes,我可以在其中逐个推送数据集(一次一个值或更大的块)。仅计算算术平均值的相当简化的版本是

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

这种累加器的一个特殊优势 class 是可以将不同数据类型的值推入同一累加器 class。

问题: 这适用于所有整型数据类型。然而,累加器 classes 也应该能够通过首先计算绝对值 |z| 来处理复数。然后将其推入蓄能器。为了推送单个值,很容易提供重载方法

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

用于通过迭代器推送数据块,但情况并不那么简单。为了正确重载,需要部分特化,因为我们需要知道实际的(完全特化的)复数类型。通常的方法是在内部结构中委托实际代码并相应地专门化它

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

在累加器 class 中,委托结构的模板化方法随后被

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

然而,这种技术存在一个问题,即如何访问累加器的私有成员 class。由于它们不同 classes 无法直接访问,而且 push_impl 的方法需要是静态的并且不能访问累加器的非静态成员。

我可以想到以下四种解决问题的方法,各有优缺点:

  1. 在每次调用 push 时创建一个 push_impl 的实例,由于额外的(可能)性能下降复制.
  2. 有一个 push_impl 的实例作为累加器 class 的成员变量,这将阻止我将不同的数据类型推入累加器,因为实例必须完全专业化。
  3. 使累加器的所有成员classpublic传*thispush_impl::push() 调用。由于封装中断,这是一个特别糟糕的解决方案。
  4. 根据单值版本实现迭代器版本,即为每个元素调用 push() 方法,(可能)由于额外的函数调用而降低性能.

请注意,上述性能下降本质上是理论上的,由于编译器的巧妙内联,可能根本没有问题,但是实际的 push 方法可能要多得多比上面的例子复杂。

一种解决方案比其他解决方案更可取还是我遗漏了什么?

致以诚挚的问候,非常感谢。

push_impl 可以是一个内部 class 模板(如果你使用 c++11)或者你的累加器 class 的朋友 class 模板(这个似乎是使用友元声明的好案例,因为 push_impl 本质上是累加器 class 实现的组成部分,纯粹出于语言原因而分开)。然后你可以使用你的选项 #3(将 this 传递给 push_impl 的静态方法),但不使累加器成员成为 public.

选项 #4 似乎也不错(因为它避免了代码重复),但正如您提到的那样,需要衡量性能影响。

就我个人而言,我可能会选择您的选项 4,毕竟迭代器版本中实际随类型变化的唯一部分是 "single value version"

中的逻辑

然而,另一种选择是编写您的迭代器版本以通过引用接收均值和大小,然后可以更新均值和大小而无需制作它们 public。

这也有助于测试,因为它允许 push_impl 单独测试(尽管使用这种方法您可能会认为这不再是函数的最佳名称)

顺便说一句,你的 push_impl 最好只在迭代器类型上进行模板化,你可以按照你目前在你的调用示例,但仅将迭代器类型作为参数,就不可能意外地使用错误的值类型调用它(如果值类型可以转换为您传递的类型,这可能并不总是导致编译错误 "T")

如评论所述,您根本不需要为此使用偏特化,实际上偏特化通常很容易避免,而且最好避免。

private:
template <class T>
struct tag{}; // trivial nested struct

template <class I, class T> 
void push_impl(I first, I last, tag<T>) { ... } // generic implementation

template <class I, class T>
void push_impl(I first, I last, tag<std::complex<T>>) { ... } // complex implementation

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

由于 push_impl 是一个(私有)成员函数,您不需要再做任何特殊的事情。

与您提出的解决方案相比,这没有额外的性能成本。它是相同数量的函数调用,唯一的区别是按值传递无状态类型,这对编译器来说是一个完全微不足道的优化。封装也没有任何牺牲。样板文件略少。