在模板 class 中专门化方法的正确方法
Proper way to specialise a method within a templated class
上下文:
我目前正在尝试实现 std::vector
,例如 class。这是一个学校项目:我们需要那样写,即原始指针,没有像std::vector
或std::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;
};
模板的想法是我的向量可以存储float
、double
、int
、std::size_t
、std::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_randomly
:distribution_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) );
}
}
}
其中:
- 出于与我的“第一次尝试”函数相同的原因(以及其他对我来说不明显的原因)不要编译:编译器尝试编译专用于
std::complex
的行,其类型为 float。
- 非常丑陋且不易阅读。
我没有看过转换(例如从双倍到复杂),但我认为这不是最好的方法。
第三种解决方案:模板专业化
注意:文字与原文相同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>
行,编译器会将不同的定义视为重载,并表示该函数无法重载。
最后,这个解决方案对我来说效果很好,再次感谢大家:)
上下文:
我目前正在尝试实现 std::vector
,例如 class。这是一个学校项目:我们需要那样写,即原始指针,没有像std::vector
或std::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;
};
模板的想法是我的向量可以存储float
、double
、int
、std::size_t
、std::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_randomly
:distribution_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) );
}
}
}
其中:
- 出于与我的“第一次尝试”函数相同的原因(以及其他对我来说不明显的原因)不要编译:编译器尝试编译专用于
std::complex
的行,其类型为 float。 - 非常丑陋且不易阅读。
我没有看过转换(例如从双倍到复杂),但我认为这不是最好的方法。
第三种解决方案:模板专业化
注意:文字与原文相同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>
行,编译器会将不同的定义视为重载,并表示该函数无法重载。
最后,这个解决方案对我来说效果很好,再次感谢大家:)