【问题标题】:Manipulate underlying range with range-v3使用 range-v3 操作基础范围
【发布时间】:2018-10-13 21:10:52
【问题描述】:

我有一个数学图书馆。在这个库中,我有在单纯形空间中操作超平面的函数,以便我可以以各种方式对它们进行排序。

事实证明,这些超平面可以在不同的上下文中表示不同的事物。虽然数学是相同的,但在每种情况下,超平面的含义不同,并且与不同的数据结构相关联。

能够编写一次操作超平面的代码对我来说是有利的,但允许它们处理不同的数据结构。

下面是一个简化的例子,试图解释我想要做什么:

// Assume this struct represent my hyperplane, or whatever
// construct I want to be able to manipulate.
struct Math {
    int x;
};

// Here is my function `foo` which expects a range of Math. It does
// some work on it, and re-arranges it as it is useful to me.
template <typename It>
void foo(It begin, It end) {
    while (begin < end) {
        --end;
        if (begin->x < end->x)
            std::iter_swap(begin, end);
        ++begin;
    }
}
template <typename Range>
void foo(Range & r) {
    foo(ranges::begin(r), ranges::end(r));
}

这基本上是我的基础功能,这对于使用我的超平面的每个附加类(或者,在本例中为 Math 类)都是通用的。

现在在我的库的其他部分,我的类看起来像这样:

struct Something {
    int additional_metadata;
    Math contextual_name_for_math;
};

struct SomethingElse {
    double other_metadata;
    std::vector<int> some_other_metadata;
    Math another_different_contextual_name;
};

现在我需要能够将foo 应用于这些类的范围,并根据它们包含的Math 的属性重新排列它们。同时:

  • foo 不知道 Math 在每个类中的上下文名称。
  • foo 不关心存在的其他元数据。

我想写的是这样的:

// Create data
std::vector<Something> S{{1,{2}},{3,{4}},{5,{6}}};

// Transform data in to view of Math, so that 'foo' can work on it
auto S_as_math = S | ranges::view::transform(
    // I guess I can remove the consts here, although `foo` does not 
    // really need to alter the values, it only shuffles things around.
    [](auto const& s) -> Math const& { return s.contextual_name_for_math; }
);

// Do work inline on the view, but hopefully on the underlying S too.
foo(S_as_math);

// Print results.
for (auto s : S) {
    std::cout << '(' << s.additional_metadata << ", " 
                     << s.contextual_name_for_math.x << ")\n";
}
std::cout << "\n";

// expected, keeps Math and associated metadata together:
//
// (5, 6)
// (3, 4)
// (1, 2)
//
// NOT WANTED, only shuffles Math with no regard for metadata:
//
// (1, 6)
// (3, 4)
// (5, 2)

目前我通过将boost::transform_iterators 传递给foo 来执行此操作,Math 在取消引用时提取组件,并在foo 中使用iter_swap 的自定义实现@ 能够知道它是否是被传递一个代理迭代器并且总是交换底层的原件。这实现了我想要的。

我很好奇使用ranges-v3 是否可以做到这一点。目前,如果我删除 lambda 中用于解开 Something 类的 consts,我可以编译此示例,但随后 foo 只会对 Maths 进行洗牌,而不会将它们与元数据保存在一起。

【问题讨论】:

    标签: c++ range-v3


    【解决方案1】:

    将您的转换函数传递给foo,不要foo 转换后的范围。

    template <typename It, typename UnaryFunction>
    void foo(It begin, It end, UnaryFunction func) {
        while (begin < end) {
            --end;
            if (func(*begin).x < func(*end).x)
                std::iter_swap(begin, end);
            ++begin;
        }
    }
    template <typename Range, typename UnaryFunction>
    void foo(Range & r, UnaryFunction func) {
        foo(ranges::begin(r), ranges::end(r));
    }
    
    int main()
    {
        std::vector<Something> S{{1,{2}},{3,{4}},{5,{6}}};
        auto S_as_math = [](auto const& s) { return s.contextual_name_for_math; };
        foo(S, S_as_math);
    
        for (auto s : S) {
            std::cout << '(' << s.additional_metadata << ", " 
                             << s.contextual_name_for_math.x << ")\n";
        }
        std::cout << "\n";   
    }
    

    如果您使用纯粹的Math 范围,您可以将原始模板或默认UnaryFunction 保留为identity function

    【讨论】:

    • 我可以这样做,但这并不能真正从ranges 库中受益,是吗?我也不太喜欢这个解决方案——对我来说,保持迭代器的复杂性要干净得多,因为它可以是任意的。这将允许所有函数的签名保持相同 - 无论是我的 foo 还是 std::sort 等等,而不强制所有函数都具有 func 参数。我认为这也是ranges 的目标?
    • @Svalorzen 转换后的范围是源范围的单独值。在foo 中投影的另一个选项,您可以要求每个Something bool math_less(const Something &amp; lhs, const Something &amp; rhs) { return lhs.contextual_name_for_math.x &lt; rhs.contextual_name_for_math.x; } 的比较器
    • 对不起,我也不喜欢这个解决方案。它假设我只需要在foo 中执行单个特定操作。对于每个可能的操作,我都需要某种比较器,此时传递一元转换函数会更容易。它还会导致大量重复代码,因为所有比较器在功能上都是完全相同的代码。
    • @Svalorzen 你可以写一个math_traits 模板化一元转换。
    猜你喜欢
    • 1970-01-01
    • 2019-10-04
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-25
    相关资源
    最近更新 更多