在模板化类

时间:2017-04-12 21:47:07

标签: c++11 templates template-specialization

上下文:

我目前正在尝试实施类std::vector类。这是一个学校项目:我们需要来编写它,即原始指针,没有STL结构std::vectorstd::valarray

因为我希望这个类尽可能通用,所以这是一个模板化的类。这是我的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结构将表现为预处理器指令,并且如果编译时条件返回false,则整个相应的块将被删除我的函数定义。但是看到这个编译错误,我认为我的第一个假设是假的。

第二次尝试:

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

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次复制粘贴后,我对自己说“所有这些复制粘贴......不正是为什么模板在这里?为了避免复制粘贴并制作通用代码?”。

然后我在论坛和搜索引擎上搜索更多,以便找到类似的问题(以及“好的”解决方案!)。我找不到它。

我想要的是什么:

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

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

最后一个问题:

有没有“好”的方式来做我想要的事情?比10或20个方法专业化(和复制粘贴)更好的东西?

PS:这是我在这个论坛上的第一篇文章。我试图尊重所有规则,我不确定结果。如果有任何问题(缺少信息,而不是通常的格式化......),请告诉我。

2 个答案:

答案 0 :(得分:0)

我建议使用一组重载来填充对应于不同类型的数据。它简化了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);
}

答案 1 :(得分:0)

编辑:编辑了帖子并找到了解决方案。请参阅斜体和&#34;解决方案&#34;一部分。

我尝试的合成:

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

注意 :查看最终解决方案。最终解决方案使用std::enable_if

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

有关问题的基本示例和简短(但更完整)说明,请参阅&#34;注释&#34;中的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. 不要编译与我的&#34;第一次尝试&#34;相同的原因函数(以及其他对我来说不明显的原因):编译器尝试使用float类型编译专用于std::complex的行。
  2. 非常丑陋且不易阅读。
  3. 我没有看过演员表(例如从复合到复杂),但我认为这不是最好的方法。

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

    注意 :文字与原始帖子中的相同。这个&#34;解决方案&#34;并不是最好的解决方案,我找到的最佳解决方案将在之后解释。

    最后,正如@R sahu提出的那样,唯一的&#34;好&#34;目前发现的解决方案I(以及我的C ++教授)专门针对我需要的每种类型,使用copy-paste。

    所以我的最终实现将是这样的:

    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在我的第一篇文章中的评论,这是我正在寻找的解决方案。该解决方案已成功测试。

    最后,我的Tvector课程如下:

    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函数的签名和返回类型是固定的。如果这些没有修复,我可以直接在我的fill_randomly函数上应用相同的技术,而不引入fill_randomly_impl函数。

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

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

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