16

Having been tainted by Linq, I'm reluctant to give it up. However, for some things I just need to use C++.

The real strength of linq as a linq-consumer (i.e. to me) lies not in expression trees (which are complex to manipulate), but the ease with which I can mix and match various functions. Do the equivalents of .Where, .Select and .SelectMany, .Skip and .Take and .Concat exist for C++-style iterators?

These would be extremely handy for all sorts of common code I write.

I don't care about LINQ-specifics, the key issue here is to be able to express algorithms at a higher level, not for C++ code to look like C# 3.0. I'd like to be able to express "the result is formed by the concatenation first n elements of each sequence" and then reuse such an expression wherever a new sequence is required - without needed to manually (and greedily) instantiate intermediates.

Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166

8 Answers8

9

I am working on (C# LINQ)-like C++ header-only library.

Here it is: http://code.google.com/p/boolinq/

I'd like to get any feedback...

UPDATE:

Here is new link to boolinq 2.0: https://github.com/k06a/boolinq

All source code is based in single header file - https://github.com/k06a/boolinq/blob/master/boolinq/boolinq.h

It is super short: less than 800 lines for about 60 different operations!

k06a
  • 17,755
  • 10
  • 70
  • 110
  • the library looks really nice and its the closest one i've seen to something working as expected. However it is not building for me on gcc 4.7 with c++11 enabled – lurscher Jul 01 '12 at 15:51
  • @lurscher, this library was developed using Visual C++. But i'd like to make it so portable and cross-platform as it possible. I have tried to supporn mingw 4.4, but it does not support C++11 lambdas. I'll add gcc support soon. Or you can help me) – k06a Jul 01 '12 at 21:15
6

I'd like to recommend the P-Stade.Oven library for your reference. This is a strongly boostified library working on STL ranges and featuring many LINQ-like functions including the equivalents of .Where, .Select .Skip .Take and .Concat.

zwvista
  • 141
  • 2
  • 1
4

See this Google Groups thread.

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); 

I couldn't find anything more or less "official" or widely accepted, but you can try contacting the author of the original post.

Anton Gogolev
  • 113,561
  • 39
  • 200
  • 288
  • That is using a c++0x compiler with auto and lambda support, it can become a little more cumbersome without lambdas (`[](int i){...}`) – David Rodríguez - dribeas Oct 14 '09 at 10:26
  • 2
    Thanks, that's neat! However, the linked thread isn't readily usable, and in any case, it looks a little too focused on LINQ - I don't care much about the syntax, I just want map/reduce/filter/concat etc... Plain old functional-style like STL accumulate suffices - as long as it's useable *today*... – Eamon Nerbonne Oct 14 '09 at 13:05
4

I don't have concrete experience with LINQ, but the Boost.Iterator library seems to approach what you're referring to.

The idea is to have functions (IIUC, in LINQ, they take the form of extension methods, but that's not fundamental), taking an iterator and a function, combining them to create a new iterator.

LINQ "Where" maps to make_filter_iterator:

std::vector<int> vec = ...;
// An iterator skipping values less than "2":
boost::make_filter_iterator(_1 > 2, vec.begin())

LINQ "Select" maps to 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())

And they can be composed:

//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())
)

However, there are a few annoying things with this:

  • The type returned by make_xxx_iterator(some_functor, some_other_iterator) is xxx_iterator<type_of_some_functor, type_of_some_iterator>
  • The type of a functor created using boost::bind, lambda, or phoenix quickly becomes unmanageably large and cumbersome to write.

That's why I avoided in the code above to assign the result of make_xxx_iterator to a variable. C++0x "auto" feature will be pretty welcome there.

But still, a C++ iterator can't live "alone": they have to come in pairs to be useful. So, even with "auto", it's still a mouthful:

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())
);

Avoiding the use of lambda makes things verbose, but manageable:

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())
);

The (not-yet)Boost.RangeEx library is promising in this respect, in that it allows to combine the two iterators in a single range. Something like:

auto filtered_and_transformed = make_transform_range(
    make_filter_range(vec, _1 > 2),
    construct<std::string>('*', _1)
);
Éric Malenfant
  • 13,938
  • 1
  • 40
  • 42
3

With Boost.Range and Linq in C++11, you can write Linq queries in a very similar way:

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);

Will output:

9
16
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
  • `from` and `where` are macros? – CodesInChaos Jun 07 '12 at 12:05
  • no, they are not. The only macro is the `LINQ` macro. The rest is parsed using the preprocessor. – Paul Fultz II Jun 07 '12 at 12:07
  • From [code inspection](http://github.com/pfultz2/Linq/blob/master/linq/extensions/where.h), ``where`` is an ``operator()`` overload given a perfectly forwarded ``Range&& r`` and the ``Predicate p``. From there the handoff is to [``Boost.Range``](http://www.boost.org/doc/libs/1_62_0/libs/range/doc/html/index.html), etc. – mwpowellhtx Dec 05 '18 at 21:46
  • Linq's link seems to be https://github.com/pfultz2/Linq/ now. – Кое Кто Oct 11 '22 at 09:35
2

Here is another alternative that is simply a wrapper around boost and stl algorithms, and thus you get all the performance benefits of those implementations.

It works like this:

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>>();

Note that some methods return a proxy for empty ranges, e.g.

std::vector<int> xs;
auto max = from(xs)
   .select([](int x){return x*x;})
   .where([](int x){return x > 16;})
   .max()
   .value_or(0);
ronag
  • 49,529
  • 25
  • 126
  • 221
  • Well done, but a few things: `void for_each(...){ return ...; }` doesn't sound right, and whenever you have `auto x(...) -> decltype(some long expression)` and you need to reuse that type inside of `x` (say, `select`, `reverse`, `take`, etc), just say `decltype(x(...))` and pass all the arguments, will for many things be *much* shorter. Also, you have `to_vector`, not `to`. :) – Xeo Oct 04 '12 at 19:54
  • Xeo: Thanks for the feedback. Fixed `to` and `for_each`. Though I don't quite understand how to get your suggestion for shortening the `decltype`s to work. Could you give me more concrete example? – ronag Oct 04 '12 at 20:05
  • In hindsight, I wonder why you need to reuse the type at all in the body, since the constructor of `linq_range` isn't `explicit`. – Xeo Oct 04 '12 at 20:05
  • Xeo: At some point it didn't work the not "reusing" the type. But that no longer seems to be a problem. I'm changing it. – ronag Oct 04 '12 at 20:08
  • I wonder if there would be any advantage to store a reference of the original container passed to `from(...)` inside the first `linq_range` instead of converting it first into a `iterator_range`. Would need some extra meta-template programming to enable the `linq_range` to store `range` as a reference or value, depending on the case. My initial guess would be that there would be no difference due to compiler optimization. – ronag Oct 04 '12 at 21:52
  • Updated code to return `boost::optional` instead of iterators. – ronag Oct 05 '12 at 06:32
  • Xeo: Another problem, `.max().get_value_or(0)` will cause a compiler warning since `max()` returns a `boost::optional` and `0` is an rvalue. Any ideas how to get around that problem? Do you think it i safe to ignore since it is returned right away? – ronag Oct 05 '12 at 06:48
  • I don't think it's completely safe to ignore, unfortunately. :/ If user does `auto& v = ... .max().get_value_or(...)`, it will blow up in his face. On the other hand, I don't see any good way to return a reference and still allow a default value. Maybe we should've just ignored `optional` and gone the "empty range" and "single range" return route, + a `first_or(val)` function that is overloaded on lvalues and rvalues. – Xeo Oct 05 '12 at 08:07
  • Hm, `first_or(val)` sounds reasonable, though I'm not yet sure how that would be easiest to implement, I don't think `boost::range` has a concatenation operation. – ronag Oct 05 '12 at 08:11
  • Why would you need concatenation? `linq_xs.max().first_or(val)` where `max()` returns a new `linq_range` that is either empty or contains one element. – Xeo Oct 05 '12 at 08:14
  • Xeo: What about instead of `boost::optional`, we could have a proxy type, `boost::optional>`? – ronag Oct 05 '12 at 08:15
  • Xeo: How would create that `linq_range` with one element. – ronag Oct 05 '12 at 08:16
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/17595/discussion-between-ronag-and-xeo) – ronag Oct 05 '12 at 08:17
  • Replaced `boost::optional` with another class `first_or` which solves the problem. – ronag Oct 05 '12 at 10:14
  • Note that this recommendation is GPL, and therefore, anything using it must also be GPL. – Hank Schultz Jun 24 '16 at 13:47
0

Personally, I have used cpplinq from time to time. It is pretty good. It does not try to be a perfect translation of LINQ by any means, and maintains enough of a C++ identity, if you will, that makes it pretty strong in and of itself. Plus, you do not take any dependencies, other than C++11 standards-wise, that is. As long as you can stomach that, you're good to go with cpplinq.

mwpowellhtx
  • 351
  • 1
  • 9
0

The Abseil library has many https://github.com/abseil/abseil-cpp/blob/master/absl/algorithm/container.h has many container functions: c_all_of, c_any_of, c_none_of, c_find, c_count, c_count_if, c_replace_copy, c_unique_copy, c_min_element, c_max_element, c_accumulate

Colonel Panic
  • 132,665
  • 89
  • 401
  • 465