Skip to content

cemderv/linq

Repository files navigation

linq - LINQ for C++

linq is a header-only LINQ library for C++ 17 and newer. It has no dependencies and neatly integrates into the STL by taking advantage of modern C++ features.

linq ...

  • resolves all type-related functionality at compile-time; no virtual dispatch is used
  • uses lazy evaluation, so your queries still work even after you modify the container it's based on
  • focuses on immutability, so your queries stay predictable
  • is efficient in the way it works with your data; it avoids copies and instead moves data wherever it can
  • generates an operation chain at compile-time
  • works with all generic container types, not just the STL
  • does not rely on exceptions and instead reports errors at compile-time
  • uses the beloved dot operator!

linq has been tested with the following compilers:

  • Clang 16+
  • GCC 10+
  • AppleClang 13+
  • MSVC 2019+

Linux-Clang Linux-GCC macOS-AppleClang Windows-MSVC

Examples

Intro

#include <linq.hpp>

struct Person {
    string name;
    int age;
};

const vector<Person> people {
    { .name = "P1", .age = 20 },
    { .name = "P2", .age = 21 },
    { .name = "P3", .age = 22 },
};
  
auto query = linq::from(&people)
                  .where( [](const Person& p) { return p.age > 20; } );
                    
for (const Person& p : query) {
    println("{}, {}", p.name, p.age);
}

Output:

P2, 21
P3, 22

Aggregation

double average_age = linq::from(&people)
                          .select( [](const Person& p) { return p.age; } )
                          .average();

int people_over_20 = linq::from(&people)
                          .where( [](const Person& p) { return p.age > 20; } )
                          .count();

Element access

const auto over_20 = [](const Person& p) { return p.age > 20; };
const auto under_20 = [](const Person& p) { return p.age < 20; };

optional<Person> first_over_20 = linq::from(&people).first(over_20);
// first_over_20 = P2, 21

optional<Person> last_over_20 = linq::from(&people).last(over_20);
// last_over_20 = P3, 22

optional<Person> first_under_20 = linq::from(&people).last(under_20);
// first_under_20 = empty optional

Partitioning

const vector numbers { 1, 2, 3, 4, 5, 6 };

auto query1 = linq::from(&numbers).skip(3); // 4, 5, 6

auto query2 = linq::from(&numbers)
                   .skip_while( [](int i) { return i < 4; } ); // 4, 5, 6

auto query3 = linq::from(&numbers).take(4); // 1, 2, 3, 4

auto query4 = linq::from(&numbers)
                   .take_while( [](int i) { return i < 4; } ); // 1, 2, 3

Sorting

const vector words { "hello"s, "world"s, "here"s, "are"s, "some"s, "sorted"s, "words"s };

auto query = linq::from(&words)
                  .order_by_ascending( [](const string& word) { return word.size(); } )
                  .then_by_ascending( [](const string& word) { return word; } );
// query = are, here, some, hello, words, world, sorted

auto rev = query.reverse();
// rev = sorted, world, words, hello, some, here, are

Concatenation

const vector numbers1 { 1, 2, 3 };
const vector numbers2 { 4, 5, 6 };

auto range = linq::from(&numbers1)
                  .append(linq::from(&numbers2));

// range = 1, 2, 3, 4, 5, 6

Removing duplicates

const vector numbers { 1, 2, 3, 3, 5, 4, 5, 6, 7 };

const auto query = linq::from(&numbers).distinct();
// query = 1, 2, 3, 5, 4, 6, 7

Composition

auto people = linq::from(&people);
auto age_over_20 = [](const Person& p) { return p.age > 20; };
auto people_over_20 = people.where(age_over_20);
auto person_age = [](const Person& p) { return p.age; };

double average_age = people_over_20.select(person_age).average();

Producing containers

to_vector:

const array numbers { 1, 2, 3, 4 };

vector vec = linq::from(&numbers)
                  .where( [](int i) { return i > 1; } )
                  .select_to_string()
                  .to_vector(); // copy elements to a new std::vector

// 'vec' is of type std::vector<std::string>
// with contents ["2", "3", "4"]

to_map:

const array pairs {
    pair{ 1, "str1"s },
    pair{ 5, "str5"s },
    pair{ -10, "str-10"s },
};

const auto query = linq::from(&pairs)
                        .where( [](const auto& p) { return p.first < 20; } );

map my_map = query.to_map();

for (const auto& [key, value] : my_map) {
    println("[{}: {}]", key, value);
}

Output:

[-10: str-10]
[1: str1]
[5: str5]

Generation

auto range1 = linq::from_to(0, 5);           // 0, 1, 2, 3, 4, 5

auto range2 = linq::from_to(0.0, 1.5, 0.5);  // 0, 0.5, 1, 1.5

auto range3 = linq::from_to(0, 3).repeat(1); // 0, 1, 2, 3, 0, 1, 2, 3

auto range4 = linq::generate([](size_t i) {
    if (i < 5)
        return linq::generate_return(i * 2);

    return linq::generate_finish<size_t>();
});
// range4 = 0, 2, 4, 6, 8

Below you will find a list of all supported functions and operators.

Query Constructors

Container Producers

Operators