Giter VIP home page Giter VIP logo

giters's Introduction

Giters - LINQ style iterators for C++

The goal is to write C++ code similar to LINQ in C# (using method syntax).
Which is to say, transforming and filtering elements in a sequence using a fluent syntax.

C# accomplishes this via extension methods.
C++ doesn't have this language feature but we can achieve similar results by overloading a binary operator.
Giters uses the pipe (|) operator because we are conceptually "piping" elements through different operations.

// C# Example
var squaredEvens = myNumbers
    .Where(n => n % 2 == 0)
    .Select(n => n * n)
    .ToList();
// C++ Example
auto squaredEvens = myNumbers
    | Where([](int n) { return n % 2 == 0; })
    | Select([](int n) { return n * n; })
    | ToVector();

// Alternatively, we could use function pointers instead of lambdas:
auto squaredEvens = myNumbers | Where(&IsEven) | Select(&Square) | ToVector();

Giters

This library implements several projections, filters, & other utilities.
Projections generally take a selector as a parameter - a function that takes an element as the input value & returns some new value.
Filters generally take a predicate as a parameter - a function that takes an element & returns a value indicating whether that element should be included in the result range.

  • Consume - iterates the entire range
  • FirstOrDefault - gets the first element in the range; returns a default-constructed object if the range is empty
  • ForEach - executes a function for each element; consumes the range
  • NonNull - keeps elements where element != nullptr & yields pointers
  • NonNullRef - keeps elements where element != nullptr & yields references
  • Select - projects each element into a new value
  • ToVector - creates a new std::vector<> containing the elements in the range
  • Visit - executes a function for each element
  • Where - keeps elements where the predicate returns true

Creating an iterable range object using Giters does not perform any iterations itself.
The evaluation of the range is said to be lazy & the range must be consumed in some way to process the results.

For example, this code creates a range object, foo, that can be iterated, but no elements have been inspected or transformed yet:

int myNumbers[] = { -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8 };
int numInspected = 0;
int numTransformed = 0;
auto foo = myNumbers
    | Where([&numInspected](int n) { ++numInspected; return n > 0; })
    | Select([&numTransformed](int n) { ++numTransformed; return n * 2; });
// `numInspected` and `numTransformed` are both zero because `foo` has not been consumed.

The object foo can be consumed in various ways & the Giter operations will be performed each time as necessary.

//                                                  numInspected =  0, numTransformed =  0
for (int n : foo) { }                            // numInspected = 12, numTransformed =  8
foo | Consume();                                 // numInspected = 24, numTransformed = 16
foo | ForEach([](int n) { /* todo: print n */ });// numInspected = 36, numTransformed = 24
std::vector<int> keepers = foo | ToVector();     // numInspected = 48, numTransformed = 32, keepers = { 2, 4, 6, 8, 10, 12, 14, 16 }
int first = foo | FirstOrDefault();              // numInspected = 53, numTransformed = 33, first = 2

In the last line above, numInspected changes from 48 to 53 & numTransformed from 32 to 33 because FirstOrDefault stops iterating after it finds its first match (in this case, the first number greater than zero).


Example

// Giters
std::vector<std::string> names = widgets
    | NonNull()
    | Visit([](const Widget* w) { LogWidgetState(*w); })
    | Where([](const Widget* w) { return w->IsEnabled(); })
    | Select[](const Widget* w) { return w->GetName(); })
    | ToVector();

// Typical C++
std::vector<std::string> names;
for (const Widget* w : widgets) {
    if (w != nullptr) {
        LogWidgetState(*w);
        if (w->IsEnabled()) {
            names.push_back(w->GetName());
        }
    }
}

Implementation Concepts & Range-Based for Loops

From cppreference.com, a range-based for loop looks like this...

for (range_declaration : range_expression) loop_statement

So our code like this...

for (const Widget& w : GetWidgets()) {
    w.PrintName();
}

...expands to something like this...

{
    auto&& __range = GetWidgets();
    auto __begin = std::begin(__range);
    auto __end = std::end(__range);
    for (; __begin != __end; ++__begin) {
        const Widget& w = *__begin;
        w.PrintName();
    }
}

This is informative because it provides us with all the details necessary to implement a solution that allows code like B instead of code like A...

std::vector<Widget*> widgets = GetWidgets();

// A (typical code)
for (Widget* w : widgets) {
    if (w != nullptr) {
        w->Refresh();
    }
}

// B (using Giters)
for (Widget& w : widgets | NonNullRef()) {
    w.Refresh();
}

We see that we need to implement the following pieces:

  1. Some operator| that returns a "__range" object
  2. Some "__range" object that can be passed to std::begin() and std::end()
  3. Some "__begin" object (an iterator) that implements...
    1. operator!= to compare against some "__end" object
    2. operator* to get the current value of the iterator
    3. operator++ to advance the iterator

That doesn't sound so hard ๐Ÿ˜€
Here is pseudo-code that illustrates the basic outline for implementing a "non-null to ref" Giter that yields references to elements that are not null (members & function implementations have been omitted for brevity):

// a "token" type used with `operator|` (see below)
struct NonNullRef { };

// operator that accepts any "range" object on the left & our `NonNullRef` token on the right
template <typename TSource>
auto operator|(TSource& source, NonNullRef) {
    return NonNullRefRange_t<TSource>(source);
}

// the "__range" type that can be passed to `std::begin()` and `std::end()`
template <typename TSource>
struct NonNullRefRange_t {
    Iter_t begin() { return Iter_t(source); } // creates an iterator that skips over null values in the source range
    SourceEnd_t end() { return std::end(source); } // returns the "__end" object that we compare against to check termination
    TSource& source; // reference to the original/input/source range

    // the "iterator" type that implements operators to...
    //  - check termination
    //  - get the current value
    //  - advance to the next non-null value
    struct Iter_t {
        Iter_t(TSource& source); // initialize this iterator by advancing to the first non-null value in the range
        bool operator!=(const SourceEnd_t& end); // returns whether this iterator has reached the end of the range
        auto& operator*(); // returns the current value of this iterator - dereferences a pointer & returns a reference
        void operator++(); // advance to the next non-null value in the range
    };
};

giters's People

Contributors

igood avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.