给定一个类型,如何派生出一个通用的更广泛的类型(例如,用于溢出安全求和)?
Given a type, how to derive a generic wider type (e.g. for overflow-safe summation)?
举个例子,假设我有一个函数计算给定 std::vector<T>
:
的平均值
template<class T>
T vec_average(const std::vector<T>& vec) {
assert(!vec.empty());
T accumulator = vec[0] - vec[0]; // Init to 0.
for (const auto& el : vec) { accumulator += el; }
return accumulator / double(vec.size());
}
现在当然要求所有运算符(+=、/、-)都为 T
定义并且可能存在舍入问题,但更严重的问题是调用 vec_average
与 "small type" 类似 uint8_t
将很快导致由于溢出导致的不良结果。
type_traits
救援!你可能会说,这确实是我解决这个问题的初步尝试:
template<class T>
struct safe_accum {
typedef typename std::conditional<std::is_integral<T>::value, int64_t, T>::type type;
};
template<class T>
T vec_average(const std::vector<T>& vec) {
assert(!vec.empty());
safe_accum<T>::type accumulator = vec[0] - vec[0]; // Init to 0.
for (const auto& el : vec) { accumulator += el; }
return accumulator / double(vec.size());
}
但是:这只解决了所有整数和浮点类型。一旦我有了结构或 class 类型,它就会崩溃(因为 std::is_integral
不适用于 class 类型):
struct Vector3uc {
uint8_t data[3];
// Operator definitions etc...
};
void foobar() {
std::vector<Vector3uc> bla;
// ...
Vector3uc avg = vec_average(bla); // Won't work!
}
因此,我的问题是:我能否拥有适用于 "classy" 输入类型 T
的 safe_accum
类型?
例如,我想告诉编译器 safe_accum<Vector3uc>::type
必须生成 Vector3ll
(int64_t 的 3 向量),而 safe_accum<Vector3f>::type
只是 Vector3f
.
我很确定它可以通过 typedefs and/or 模板专业化来完成,但在我的 C++ 知识中这是一个灰色地带...
PS: 需要说明的是,我不介意在其中手动定义这些翻译 (Vector3uc
--> Vector3ll
)每个相关类型的代码,只要每个类型只有一两行。
你可以给你的特质更多的自定义点:
template <class T, typename Enabler = void>
struct safe_accum
{
using type = T;
};
template <class T>
struct safe_accum<T, std::enable_if_t<std::is_integral<T>::value>>
{
using type = int64_t;
};
class Vector3uc;
class Vector3ll;
template <>
struct safe_accum<Vector3uc>
{
using type = Vector3ll;
};
// ...
您可以引入一个类型特征,safe_accum
将使用它来获取中间累加器类型。
template< typename T >
struct accumulator_type;
template< >
struct accumulator_type< float > { typedef float type; };
template< >
struct accumulator_type< double > { typedef double type; };
template< >
struct accumulator_type< char > { typedef int type; };
template< >
struct accumulator_type< unsigned char > { typedef unsigned int type; };
// etc. - you have to specialize it for every type you want to accumulate
template<class T>
T vec_average(const std::vector<T>& vec) {
assert(!vec.empty());
typename accumulator_type<T>::type accumulator{}; // Init to 0.
for (const auto& el : vec) { accumulator += el; }
return accumulator / double(vec.size());
}
您可以使用 Boost.Integer 等工具在 accumulator_type
实现中为累加器自动 select 更大的整数类型。
举个例子,假设我有一个函数计算给定 std::vector<T>
:
template<class T>
T vec_average(const std::vector<T>& vec) {
assert(!vec.empty());
T accumulator = vec[0] - vec[0]; // Init to 0.
for (const auto& el : vec) { accumulator += el; }
return accumulator / double(vec.size());
}
现在当然要求所有运算符(+=、/、-)都为 T
定义并且可能存在舍入问题,但更严重的问题是调用 vec_average
与 "small type" 类似 uint8_t
将很快导致由于溢出导致的不良结果。
type_traits
救援!你可能会说,这确实是我解决这个问题的初步尝试:
template<class T>
struct safe_accum {
typedef typename std::conditional<std::is_integral<T>::value, int64_t, T>::type type;
};
template<class T>
T vec_average(const std::vector<T>& vec) {
assert(!vec.empty());
safe_accum<T>::type accumulator = vec[0] - vec[0]; // Init to 0.
for (const auto& el : vec) { accumulator += el; }
return accumulator / double(vec.size());
}
但是:这只解决了所有整数和浮点类型。一旦我有了结构或 class 类型,它就会崩溃(因为 std::is_integral
不适用于 class 类型):
struct Vector3uc {
uint8_t data[3];
// Operator definitions etc...
};
void foobar() {
std::vector<Vector3uc> bla;
// ...
Vector3uc avg = vec_average(bla); // Won't work!
}
因此,我的问题是:我能否拥有适用于 "classy" 输入类型 T
的 safe_accum
类型?
例如,我想告诉编译器 safe_accum<Vector3uc>::type
必须生成 Vector3ll
(int64_t 的 3 向量),而 safe_accum<Vector3f>::type
只是 Vector3f
.
我很确定它可以通过 typedefs and/or 模板专业化来完成,但在我的 C++ 知识中这是一个灰色地带...
PS: 需要说明的是,我不介意在其中手动定义这些翻译 (Vector3uc
--> Vector3ll
)每个相关类型的代码,只要每个类型只有一两行。
你可以给你的特质更多的自定义点:
template <class T, typename Enabler = void>
struct safe_accum
{
using type = T;
};
template <class T>
struct safe_accum<T, std::enable_if_t<std::is_integral<T>::value>>
{
using type = int64_t;
};
class Vector3uc;
class Vector3ll;
template <>
struct safe_accum<Vector3uc>
{
using type = Vector3ll;
};
// ...
您可以引入一个类型特征,safe_accum
将使用它来获取中间累加器类型。
template< typename T >
struct accumulator_type;
template< >
struct accumulator_type< float > { typedef float type; };
template< >
struct accumulator_type< double > { typedef double type; };
template< >
struct accumulator_type< char > { typedef int type; };
template< >
struct accumulator_type< unsigned char > { typedef unsigned int type; };
// etc. - you have to specialize it for every type you want to accumulate
template<class T>
T vec_average(const std::vector<T>& vec) {
assert(!vec.empty());
typename accumulator_type<T>::type accumulator{}; // Init to 0.
for (const auto& el : vec) { accumulator += el; }
return accumulator / double(vec.size());
}
您可以使用 Boost.Integer 等工具在 accumulator_type
实现中为累加器自动 select 更大的整数类型。