C++类LINQ的迭代器操作

16

因为受到Linq的影响,我不愿放弃它。然而,有些事情我只能使用C++。

Linq的真正优势在于我作为一个Linq消费者时(也就是对我来说),不在于表达式树(这很难操作),而在于我可以轻松地混合和匹配各种函数。是否存在与C++风格迭代器等价的功能,例如.Where, .Select, .SelectMany, .Skip, .Take.Concat?

这些将非常有用,适用于我编写的各种常见代码。

我不关心Linq的具体细节,这里的关键问题是能够以更高的层次表达算法,而不是让C++代码看起来像C#3.0。我想能够表达“结果是由每个序列的前n个元素连接而成”,然后可以在需要新序列的任何地方重用这样的表达式 - 而无需手动(且贪婪地)实例化中间变量。


可能是重复问题:https://dev59.com/k3VC5IYBdhLWcg3woCrN -- 但它们可能有所不同,我一直怀疑这是否真的是一个“完全相同”的重复。 - David Rodríguez - dribeas
2
我对LINQ本身并不感兴趣,但是对于C++中的函数式列表处理非常感兴趣。 - Eamon Nerbonne
具体来说,Lambda表达式很好,但并不是必须的。 - Eamon Nerbonne
8个回答

10

该库看起来非常不错,是我见过的最接近预期工作的库。但是,在启用了C++11的gcc 4.7上,它却无法为我构建。 - lurscher
@lurscher,这个库是使用Visual C++开发的。但我想尽可能地使它具有可移植性和跨平台性。我已经尝试支持mingw 4.4,但它不支持C++11 lambda表达式。我很快会添加gcc支持。或者你可以帮助我) - k06a

6

我想向您推荐P-Stade.Oven库作为参考。这是一个强力增强的库,适用于STL范围,并具有许多类似于LINQ的函数,包括.Where、.Select、.Skip、.Take和.Concat的等效函数。


4

请查看这个Google Groups讨论串。

vector<int> numbers = {1, 2, 3, 4, 8, 5, 9 , 24, 19, 15, 12 } 
auto query = 
    from(numbers).
        where([](int i) { return i < 15 && i > 10}). 
        select(fields::full_object); 

我没有找到更多或更少“官方”的或被广泛接受的东西,但你可以尝试联系原帖作者。


使用支持auto和lambda的c++0x编译器,如果没有lambda表达式([](int i){...}),可能会变得有些繁琐。 - David Rodríguez - dribeas
2
谢谢,这很棒!然而,链接的线程并不方便使用,而且无论如何,它似乎过于专注于LINQ - 我对语法并不太在意,我只想要map/reduce/filter/concat等等...像STL accumulate那样的传统函数式编程风格就足够了 - 只要它在今天可用... - Eamon Nerbonne

4
我没有具体的LINQ经验,但Boost.Iterator库似乎接近您所提到的内容。
其思想是有函数(我理解的是,在LINQ中,它们采用扩展方法的形式,但这并不是基本的),需要一个迭代器和一个函数,将它们组合起来创建一个新的迭代器。
LINQ的“Where”对应于make_filter_iterator
std::vector<int> vec = ...;
// An iterator skipping values less than "2":
boost::make_filter_iterator(_1 > 2, vec.begin())

LINQ中的“Select”映射到make_transform_iterator
using namespace boost::lambda;
//An iterator over strings of length corresponding to the value
//of each element in "vec"
//For example, 2 yields "**", 3 "***" and so on.
boost::make_transform_iterator(construct<std::string>('*', _1), vec.begin())

它们可以被组合:

//An iterator over strings of length corresponding to the value of each element
// in "vec", excluding those less than 2
std::vector<int> vec = ...;
boost::make_transform_iterator(construct<std::string>('*', _1), 
    boost::make_filter_iterator(_1 > 2, vec.begin())
)

然而,这里有一些令人烦恼的问题:
  • make_xxx_iterator(some_functor, some_other_iterator)返回的类型是xxx_iterator<type_of_some_functor, type_of_some_iterator>
  • 使用boost::bind、lambda或phoenix创建的函数对象类型很快变得难以管理和编写。
这就是为什么在上面的代码中我避免将make_xxx_iterator的结果赋值给一个变量。C++0x的"auto"特性在这里非常受欢迎。
但是,C++迭代器不能单独存在:它们必须成对出现才能有用。因此,即使使用"auto",它仍然很冗长:
auto begin = make_transform_iterator(construct<std::string>('*', _1), 
    make_filter_iterator(_1 > 2, vec.begin())
);
auto end = make_transform_iterator(construct<std::string>('*', _1), 
    make_filter_iterator(_1 > 2, vec.end())
);

避免使用lambda会让事情变得啰嗦,但仍然可控:
struct MakeStringOf{
    MakeStringOf(char C) : m_C(C){}
    char m_C;

    std::string operator()(int i){return std::string(m_C, i);}
};

struct IsGreaterThan{
    IsGreaterThan(int I) : m_I(I){}
    int m_I;

    bool operator()(int i){return i > m_I;}
};

typedef boost::filter_iterator<
   IsGreaterThan, 
   std::vector<int>::iterator
> filtered;

typedef boost::transform_iterator<
   MakeStringOf, 
   filtered
> filtered_and_transformed;

filtered_and_transformed begin(
    MakeStringOf('*'), 
    filtered(IsGreaterThan(2), vec.begin())
);

filtered_and_transformed end(
    MakeStringOf('*'), 
    filtered(IsGreaterThan(2), vec.end())
);

(尚未)Boost.RangeEx库在这方面很有潜力,因为它允许将两个迭代器组合成单个范围。类似这样:

auto filtered_and_transformed = make_transform_range(
    make_filter_range(vec, _1 > 2),
    construct<std::string>('*', _1)
);

3

通过在C++11中使用Boost.RangeLinq,你可以以非常相似的方式编写Linq查询:

std::vector<int> numbers = { 1, 2, 3, 4 };
auto r = LINQ(from(x, numbers) where(x > 2) select(x * x));
for (auto x : r) printf("%i\n", x);

将输出:

9
16

fromwhere 是宏吗? - CodesInChaos
不,它们不是。唯一的宏是 LINQ 宏。其余部分使用预处理器解析。 - Paul Fultz II
code inspection来看,“where”是一个“operator()”重载,给定了一个完美转发的“Range&& r”和“Predicate p”。从那里开始,就交给了Boost.Range,等等。 - mwpowellhtx
Linq的链接现在似乎是https://github.com/pfultz2/Linq/。 - Кое Кто

2
这是另一种与boost和stl算法简单包装的替代方案,因此您将获得这些实现的所有性能优势。
它的工作原理如下:
std::vector<int> xs;
auto count = from(xs)
   .select([](int x){return x*x;})
   .where([](int x){return x > 16;})
   .count();
auto xs2 = from(xs)
   .select([](int x){return x*x;})
   .to<std::vector<int>>();

请注意,一些方法会返回一个空范围的代理,例如:
std::vector<int> xs;
auto max = from(xs)
   .select([](int x){return x*x;})
   .where([](int x){return x > 16;})
   .max()
   .value_or(0);

干得好,但还有几点需要注意:void for_each(...){ return ...; }听起来不太对,而且每当你有auto x(...) -> decltype(some long expression)并且你需要在x内重用该类型(比如说selectreversetake等),只需使用decltype(x(...))并传递所有参数,这样做会更加简洁。另外,你应该使用to<T>而不是to_vector。 :) - Xeo
Xeo:另一个问题是,.max().get_value_or(0) 会导致编译器警告,因为 max() 返回一个 boost::optional<const int&>0 是一个右值。有什么想法解决这个问题吗?你认为忽略它是否安全,因为它立即返回? - ronag
Xeo:我们可以考虑使用代理类型boost::optional<value_or_ref<value_type>>来代替boost::optional<const value_type&>,您觉得呢? - ronag
Xeo:如何创建只包含一个元素的 linq_range - ronag
用另一个类first_or替换了 boost::optional,解决了这个问题。 - ronag
显示剩余10条评论

0

就我个人而言,我有时会使用cpplinq。它非常不错。它并不试图成为LINQ的完美翻译,保持了足够的C++特性,使其本身非常强大。此外,除了符合C++11标准之外,您不需要任何依赖项。只要您能接受这一点,您就可以使用cpplinq


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接