在模板 class 中专门化方法的正确方法

Proper way to specialise a method within a templated class

上下文:

我目前正在尝试实现 std::vector,例如 class。这是一个学校项目:我们需要那样写,即原始指针,没有像std::vectorstd::valarray那样的STL结构。

因为我希望这个 class 尽可能通用,所以这是一个模板化的 class。这是我的 Tvector.h 文件的摘录:

template <typename T>
class Tvector {
public:
    Tvector();
    // Other constructors and methods
    void fill_randomly();
private:
    T * _data;
    std::size_t _data_size;
    std::size_t _allocated_size;
};

模板的想法是我的向量可以存储floatdoubleintstd::size_tstd::complex<double>类型的数据(我目前需要的),如果可能的话甚至更多。

问题:

我目前在实施方法 void Tvector<T>::fill_randomly() 时遇到问题,正如它的名字所说,应该用随机数据填充我的向量。

我尝试过的:

第一次尝试:

template <typename T>
void Tvector<T>::fill_randomly() {
    std::random_device rand_device;

    if( std::is_integral<T>::value ) {
        std::uniform_int_distribution<T> distribution_int;
        // Then fill the vector
        for(std::size_t i{0}; i < this->_data_size; ++i)
            this->_data[i] = distribution(rand_device);

    } else if( std::is_floating_point<T>::value ) {
        std::uniform_real_distribution<T> distribution_real;
        // Then fill the vector
        for(std::size_t i{0}; i < this->_data_size; ++i)
            this->_data[i] = distribution(rand_device);

    } else if( /* is complex type */ ) {
        // More code looking like the 2 previous if-blocks

    } else {
        // Non-supported type
        static_assert( true, "The given type for Tvector is not supported by fill_randomly." );
    }
}

当然,我尝试创建一个 Tvector<double> 并在其上调用 fill_randomlydistribution_int.

声明的编译错误

我假设我的带有编译时条件的 if 结构将表现为预处理器指令,并且如果编译时条件 returns 为假,整个相应的块将被我的函数擦除定义。但是看到这个编译错误,我认为我的第一个假设是错误的。

第二次尝试:

经过一些研究,我发现我可以像这样专门化我的模板方法:

template <>
void Tvector<int>::fill_randomly() {
    std::random_device rand_device;
    std::uniform_int_distribution<int> distribution;
    // Then fill the vector
    for(std::size_t i{0}; i < this->_data_size; ++i)
        this->_data[i] = distribution(rand_device);
}

好的,好的,让我们为我需要的类型做这个。但是在将近 10 次复制粘贴之后我对自己说 "all theses copy-pastes... isn't it exactly why template are here? To avoid copy-pasting and make generic code?".

然后我在论坛和搜索引擎上进行了更多搜索,以找到类似的问题(以及 "good" 解决方案!)。我找不到它。

我想要的:

如果可能的话,我希望我的 fill_randomly 方法的实现与我的第一次尝试完全相同,并且可以轻松扩展和修改。

当然我问的不是实现本身,而是正确的实现方式。

最后一题:

有没有 "good" 方法来做我想做的事?比 10 或 20 种方法专业化(和复制粘贴)更好的东西?

PS: 这是我在这个论坛的第一个post。我试图遵守所有规则,我不确定结果。如果有任何问题(缺少信息,格式不正常,...),请告诉我。

我会推荐使用一组重载来填充不同类型对应的数据。它简化了 Tvector::fill_randomly().

的实现
// Non-member functions to fill random values in the data array.

template <template <class> typename Distribution, typename T> 
void fill_random_values(T* data, size_t size)
{
   std::random_device rand_device;
   Distribution<T> distribution;
   for(std::size_t i{0}; i < size; ++i)
      data[i] = distribution(rand_device);
}

void fill_random_values(int* data, size_t size)
{
   fill_random_values<std::uniform_int_distribution>(data, size);
}

void fill_random_values(long* data, size_t size)
{
   fill_random_values<std::uniform_int_distribution>(data, size);
}

void fill_random_values(float* data, size_t size)
{
   fill_random_values<std::uniform_real_distribution>(data, size);
}

void fill_random_values(double* data, size_t size)
{
   fill_random_values<std::uniform_real_distribution>(data, size);
}

// Add other overloads of fill_random_values as necessary.

template <typename T>
void Tvector<T>::fill_randomly()
{
   fill_random_values(_data, _data_size);
}

编辑:post 已编辑并找到解决方案。请参阅斜体和“解决方案”部分的评论。

综合我试过的:

第一个解决方案:std::enable_if

:见最终解法。最终的解决方案使用std::enable_if.

由于模板机制以及我的模板参数不在 fill_randomly 签名中,该解决方案无法工作。

有关问题的基本示例和简短(但更完整)的解释,请参阅“注释”部分中的 here

第二种解决方案:std::conditional

已给出此解决方案 here。在我的具体情况下,它应该可以正常工作。但这有点丑陋(个人观点),特别是如果你有长期条件。就我而言,如果我使用该解决方案,我的 fill_randomly 代码将如下所示:

template <typename T>
void Tvector<T>::fill_randomly() {
    std::random_device rand_device;
    
    using my_distribution = std::conditional<std::is_integral<T>::value,
                                             std::uniform_int_distribution<T>,
                                             std::conditional<std::is_floating_point<T>::value
                                                              || std::is_same< T, std::complex<float> >::value
                                                              || std::is_same< T, std::complex<double> >::value 
                                                              || std::is_same< T, std::complex<long double> >::value,
                                                              std::uniform_real_distribution<T>,
                                                              void>
                                             >;

    my_distribution distribution;
    // Then fill the vector
    for(std::size_t i{0}; i < this->_data_size; ++i) {
        if( std::is_floating_point<T>::value ) {
            this->_data[i] = distribution(rand_device);
        } else {
            this->_data[i] = std::complex<typename T::value_type>( distribution(rand_device),
                                                                   distribution(rand_device) );
        }
    }
}

其中:

  1. 出于与我的“第一次尝试”函数相同的原因(以及其他对我来说不明显的原因)不要编译:编译器尝试编译专用于 std::complex 的行,其类型为 float。
  2. 非常丑陋且不易阅读。

我没有看过转换(例如从双倍到复杂),但我认为这不是最好的方法。

第三种解决方案:模板专业化

注意:文字与原文相同post。这个“解决方案”不是最好的,我找到的最好的解决方案在后面解释。

最后,正如@R sahu 所建议的,我(以及我的 C++ 教授)目前发现的唯一“好”解决方案是通过复制粘贴专门针对我需要的每种类型。

所以我的最终实现将如下所示:

template <>
void Tvector<double>::fill_randomly() {
    std::random_device rand_device;
    std::uniform_real_distribution<double> distribution;
    // Then fill the vector
    for(std::size_t i{0}; i < this->_data_size; ++i)
        this->_data[i] = distribution(rand_device);
}

template <>
void Tvector<std::complex<double>>::fill_randomly() {
    std::random_device rand_device;
    std::uniform_real_distribution<double> distribution;
    // Then fill the vector
    for(std::size_t i{0}; i < this->_data_size; ++i)
        this->_data[i] = std::complex<double>( distribution(rand_device),
                                               distribution(rand_device) );
}

每当我需要一个尚未实现的类型时,我都会更新我的重载。

这很丑陋,但这是我找到的唯一解决方案。

解决方案:

感谢@mascoj 在我的第一个 post 中的评论,这是我正在寻找的解决方案。此解决方案已成功测试。

最后我的 Tvector class 看起来像这样:

template <typename T>
class Tvector {
public:
    Tvector();
    // Other constructors and methods

    // Should be in .cpp, is here for simplicity of the post.
    void fill_randomly() {
        // Call to the ONLY implemented function fill_randomly_impl().
        this->fill_randomly_impl();
    }
    
private:
    
    template <typename Integer = T>
    typename std::enable_if< std::is_integral<Integer>::value, Integer >::type fill_randomly_impl() {
        // Implementation of the radom filling for integers.
        return Integer();
    }

    template <typename Real = T>
    typename std::enable_if< std::is_floating_point<Real>::value, Real >::type fill_randomly_impl() {
        // Implementation of the radom filling for real numbers.        
        return Real();
    }

    // Other definitions of fill_randomly_impl for other types like complex types.
    
    T * _data;
    std::size_t _data_size;
    std::size_t _allocated_size;
};

首先,fill_randomly_impl函数的存在只是因为fill_randomly函数的签名和return类型是固定的。如果这些问题没有解决,我完全可以直接在我的 fill_randomly 函数上应用相同的技术,而无需引入 fill_randomly_impl 函数。

为什么template定义在方法实现之前?

如果没有 template <typename Integer = T> 行,编译器会将不同的定义视为重载,并表示该函数无法重载。

最后,这个解决方案对我来说效果很好,再次感谢大家:)