是否可以让算法通用地处理对象的成员?

问题描述 投票:0回答:1

我仍然不确定标题是否正确,因为我不知道答案,所以欢迎提出改进的建议。

我需要一种方法来计算某些数字容器的范围(最小值和最大值),而这些容器可能包含未明确定义

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++ generics
1个回答
0
投票

你的代码处理单个值,要使其处理具有多个值的结构,你需要使用大量可变参数模板,这在 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';

}

godbolt 演示

请注意,您需要 T 是默认可构造的,并且需要指定访问器函数(&vec::x、&vec::y),使用 C++26 反射或 boost pfr 可以实现更通用的功能。

© www.soinside.com 2019 - 2024. All rights reserved.