我仍然不确定标题是否正确,因为我不知道答案,所以欢迎提出改进的建议。
我需要一种方法来计算某些数字容器的范围(最小值和最大值),而这些容器可能包含未明确定义
operator<
的类型;所以我需要一种方法将他们的区间成员视为 PoD 来进行比较。
我有一个带有某些值的句柄范围的模板,比方说:
template <typename T>
struct extents {
// Initialized this "strange" way for further search min/max in containers
T min = std::numeric_limits<T>::max(),
max = std::numeric_limits<T>::lowest();
void update(T value) {
min = std::min(min, value);
max = std::max(max, value);
}
T midpoint() const {
return std::midpoint(min, max);
}
};
它适用于像这样的 PoD 类型:
float a[] = { 1.0f, 5.0f, 2.0f, 3.0f };
extents<float> ext;
for (auto v : a) {
ext.update(v);
}
(嗯,我知道
std::minmax
可以在这里使用,有时不能)。
这里的“范围”是指元素容器的最小和最大值。
当涉及到复合类型时,我必须定义
operator<
,这并不是微不足道的(就像我的向量示例一样)。我肯定需要一些与用户(数学家、游戏开发人员等)期望不同的东西。我需要检查组件的任何是否小于另一个复合值中的对应组件,所以我不希望而且通常无法在这里定义operator<
。我可以引入单独的方法,但这并不总是可行的。即使我这样做,也会对性能造成影响,因为我将被迫在大多数步骤中将 vec2 作为对象处理,而我只需要成员值。
有没有办法让这样的代码工作?
vec2 va[] = { {1.0f, 3.0f}, {5.0f, 2.0f} , {1.0f, 3.0f} };
extents<vec2> vext;
for (auto v : va) {
vext.update(v); // This won't compile because of reasons in text of the question
}
我可以手动完成所有这些,问题是,我能否以某种方式将其包装在
extents
类及其用法中,以便对于 PoD 类型中的任何类型复合自动完成,将成员的处理保留在我的责任范围内或允许我控制要带哪些成员?
我想提供如下解决方案:
vec2 va[] = { {1.0f, 3.0f}, {5.0f, 2.0f} , {1.0f, 3.0f} };
extents<vec2> vext(&vec2::x, &vec2::y);
for (auto v : va) {
vext.update(v);
}
这意味着“在比较每个成员时将它们独立地视为 SOA(结构或数组)或通道,在进行分配时将其视为单个单元 AOS(结构数组)”。
换句话说,我希望我的代码执行以下操作:
vec2 va[] = { {1.0f, 3.0f}, {5.0f, 2.0f} , {1.0f, 3.0f} };
extents<vec2>;
for (auto v : va) {
v.x.min = std::min(v.x.min,v);
v.y.min = std::min(v.y.min,v);
v.x.max = std::max(v.x.max,v);
v.y.max = std::max(v.y.max,v);
}
但是四行中没有 x、y、min/max 的混乱,其中一个
update
就足够了。
我认为它应该以某种方式起作用
std::ranges::sort(va, std::less<>{}, &vec2::x);
有效。我只需要一种方法来指定
update
算法的成员。上下文似乎非常接近:我需要一些算法来基于某些内部成员工作。
你的代码处理单个值,要使其处理具有多个值的结构,你需要使用大量可变参数模板,这在 C++17 中可以使用 fold 表达式,旧的 C++ 版本将不得不做很多事情更多技巧。
#include <limits>
#include <numeric>
#include <tuple>
#include <iostream>
template <typename T, typename...Getters>
struct extents {
std::tuple<Getters...> getters;
T min;
T max;
extents(Getters&&...g)
: getters{ g... } {
std::apply([&](auto...f) {
((min.*f = std::numeric_limits<std::decay_t<decltype(std::declval<T>().*f)>>::max()), ...);
((max.*f = std::numeric_limits<std::decay_t<decltype(std::declval<T>().*f)>>::lowest()), ...);
}, getters);
}
void update(T value) {
std::apply([&](auto...f)
{
((min.*f = std::min(min.*f, value.*f)), ...);
((max.*f = std::max(max.*f, value.*f)), ...);
}, getters);
}
T midpoint() const {
T t;
std::apply([&](auto...f)
{
((t.*f = (min.*f + max.*f) / 2), ...);
},getters);
return t;
}
};
template <typename T, typename...Getters>
auto make_extents(Getters&&...g) -> extents<T, Getters...>
{
return extents<T, Getters...>{std::forward<Getters>(g)...};
}
struct vec2 {
float x = 0.0f, y = 0.0f;
};
int main()
{
vec2 va[] = { {1.0f, 3.0f}, {5.0f, 2.0f} , {1.0f, 3.0f} };
auto vext = make_extents<vec2>(&vec2::x, &vec2::y);
for (auto v : va) {
vext.update(v);
}
std::cout << vext.midpoint().x << '\n';
std::cout << vext.midpoint().y << '\n';
}
请注意,您需要 T 是默认可构造的,并且需要指定访问器函数(&vec::x、&vec::y),使用 C++26 反射或 boost pfr 可以实现更通用的功能。