部分成员函数模板专门化和数据成员访问 [英] Partial member function template specialisation and data member access

查看:66
本文介绍了部分成员函数模板专门化和数据成员访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对模板成员函数的部分专业化有疑问.

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.

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

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

但是,此技术存在一个问题,即如何访问累加器类的私有成员.由于它们是不同的类,因此无法直接访问,而且push_impl的方法必须是静态的,并且不能访问累加器的非静态成员.

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:

  1. 在每次对 push 的调用中创建一个 push_impl 的实例,(可能)由于复制过多而导致性能降低.
  2. 具有 push_impl 的实例作为累加器类的成员变量,这将阻止我将不同的数据类型推入累加器,因为该实例必须完全专门化.
  3. 使累加器类的所有成员公开,并将 * this 传递给 push_impl :: push()调用.由于封装中断,这是一个特别糟糕的解决方案.
  4. 以单值版本来实现迭代器版本,即为每个元素调用 push()方法,由于(可能)由于额外的函数调用而导致性能降低.
  1. Create an instance of push_impl in each call to push with (possible) decrease in performance due to the extra copy.
  2. 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.
  3. 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.
  4. 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.

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

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?

最诚挚的问候,

推荐答案

如前所述,您根本不需要为此使用部分专业化,实际上,部分专业化通常很容易避免,并且更愿意避免./p>

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.

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是(私有)成员函数,因此您无需再执行任何特殊操作.

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.

这篇关于部分成员函数模板专门化和数据成员访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆