An open API service indexing awesome lists of open source software.

https://github.com/Kaaserne/cpp-lazy

C++11 (and onwards) library for lazy evaluation
https://github.com/Kaaserne/cpp-lazy

Last synced: about 2 months ago
JSON representation

C++11 (and onwards) library for lazy evaluation

Awesome Lists containing this project

README

          

[![Build status](https://github.com/Kaaserne/cpp-lazy/workflows/Continuous%20Integration/badge.svg)](https://github.com/Kaaserne/cpp-lazy/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![CodeQL](https://github.com/Kaaserne/cpp-lazy/actions/workflows/codeql.yml/badge.svg)](https://github.com/Kaaserne/cpp-lazy/actions/workflows/codeql.yml)

![](https://i.ibb.co/ccn2V8N/Screenshot-2021-05-05-Make-A-High-Quality-Logo-In-Just-5-Minutes-For-Under-30-v1-cropped.png)

Examples can be found [here](https://github.com/Kaaserne/cpp-lazy/tree/master/examples). Installation can be found [here](https://github.com/Kaaserne/cpp-lazy#installation).

# cpp-lazy
`cpp-lazy` is an easy and fast lazy evaluation library for C++11/14/17/20. The library tries to reduce redundant data usage for begin/end iterator pairs. For instance: `lz::random_iterable::end()` will return a `lz::default_sentinel_t` to prevent duplicate data that is also present in `lz::random_iterable::begin()`. If a 'symmetrical' end-begin iterator pair is needed, one can use `lz::common` or `lz::common_random`. Generally, `lz` *forward* iterators will return a `lz::default_sentinel_t` (or if the input iterable is sentinelled) because forward iterators can only go forward, so there is no need to store the end iterator, is the philosophy. Lz random access iterators can also return a `default_sentinel` if the internal data of `begin` can already decide whether end is reached, such as `lz::repeat`.

The library uses one optional dependency: the library `{fmt}`, more of which can be found out in the [installation section](https://github.com/Kaaserne/cpp-lazy#Installation). This dependency is only used for printing and formatting.

# Features
- C++11/14/17/20 compatible
- C++20's module compatible
- Easy printing/formatting using `lz::format`, `fmt::print` or `std::cout`
- One optional dependency ([`{fmt}`](https://github.com/fmtlib/fmt)), can be turned off by using option `CPP-LAZY_USE_STANDALONE=TRUE`/`set(CPP-LAZY_USE_STANDALONE TRUE)` in CMake
- STL compatible (if the input iterable is not sentinelled, otherwise use `lz::*` equivalents)
- Little overhead, as little data usage as possible
- Any compiler with at least C++11 support should be suitable
- [Easy installation](https://github.com/Kaaserne/cpp-lazy#installation)
- [Clear Examples](https://github.com/Kaaserne/cpp-lazy/tree/master/examples)
- Piping/chaining using `|` operator
- Tested with very strict GCC/Clang/MSVC flags (https://github.com/Kaaserne/cpp-lazy/blob/master/tests/CMakeLists.txt#L87)
- Bidirectional sentinelled iterables can be reversed using `lz::common`

# What is lazy?
Lazy evaluation is an evaluation strategy which holds the evaluation of an expression until its value is needed. In this library, this is the case for all iterables/iteartors. It holds all the elements that are needed for the operation:
```cpp
std::vector vec = {1, 2, 3, 4, 5};

// No evaluation is done here, function is stored and a reference to vec
auto mapped = lz::map(vec, [](int i) { return i * 2; });
for (auto i : mapped) { // Evaluation is done here
std::cout << i << " "; // prints "2 4 6 8 10 "
}
```

# Basic usage
```cpp
#include
#include

int main() {
std::array arr = {1, 2, 3, 4};
auto result = lz::map(arr, [](int i) { return i + 1; })
| lz::to(); // == {2, 3, 4, 5}
// or
auto result = arr | lz::map([](int i) { return i + 1; })
| lz::to(); // == {2, 3, 4, 5}

// Some iterables will return sentinels, for instance:
// (specific rules about when sentinels are returned can be found in the documentation):
std::vector vec = {1, 2, 3, 4};
auto forward = lz::c_string("Hello World"); // .end() returns default_sentinel_t

// inf_loop = {1, 2, 3, 4, 1, 2, 3, 4, ...}
auto inf_loop = lz::loop(vec); // .end() returns default_sentinel_t

// random = {random number between 0 and 32, total of 4 numbers}
auto random = lz::random(0, 32, 4); // .end() returns default_sentinel_t
}
```

## Philosophy behind cpp-lazy
// TODO, write about when sentinelled

## Ownership
`lz` iterables will hold a reference to the input iterable if the input iterable is *not* inherited from `lz::lazy_view`. This means that the `lz` iterables will hold a reference to (but not excluded to) containers such as `std::vector`, `std::array` and `std::string`, as they do not inherit from `lz::lazy_view`. This is done by the class `lz::maybe_owned`. This can be altered using `lz::copied` or `lz::as_copied`. This will copy the input iterable instead of holding a reference to it. This is useful for cheap to copy iterables that are not inherited from `lz::lazy_view` (for example `boost::iterator_range`).

```cpp
#include

struct non_lz_iterable {
int* _begin{};
int* _end{};

non_lz_iterable(int* begin, int* end) : _begin{ begin }, _end{ end } {
}

int* begin() {
return _begin;
}
int* end() {
return _end;
}
};

int main() {
std::vector vec = {1, 2, 3, 4};
// mapped will hold a reference to vec
auto mapped = lz::map(vec, [](int i) { return i + 1; });
// filtered does NOT hold a reference to mapped, but mapped still holds a reference to vec
auto filtered = lz::filter(mapped, [](int i) { return i % 2 == 0; });

auto random = lz::random(0, 32, 4);
// str will *not* hold a reference to random, because random is a lazy iterable and is trivial to copy
auto str = lz::map(random, [](int i) { return std::to_string(i); });

lz::maybe_owned> ref(vec); // Holds a reference to vec

using random_iterable = decltype(random);
lz::maybe_owned ref2(random); // Does NOT hold a reference to random

non_lz_iterable non_lz(vec.data(), vec.data() + vec.size());
lz::maybe_owned ref(non_lz); // Holds a reference of non_lz! Watch out for this!

// Instead, if you don't want this behaviour, you can use `lz::copied`:
lz::copied copied(non_lz); // Holds a copy of non_lz = cheap to copy
// Or use the helper function:
copied = lz::as_copied(non_lz); // Holds a copy of non_lz = cheap to copy
}
```

## Iterating
Iterating over iterables with sentinels using range-based for loops is possible. However, a workaround for C++ versions < 17 is needed.

```cpp
#include
#include

int main() {
auto iterable_with_sentinel = lz::c_string("Hello World");
// Possible in C++17 and higher
for (auto i : iterable_with_sentinel) {
std::cout << i; // prints "Hello World"
}

// Possible in C++11 - 14
lz::for_each(iterable_with_sentinel, [](char i) { std::cout << i; }); // prints "Hello World"
}
```

## Formatting
Formatting is done using `{fmt}` or ``. If neither is available, it will use `std::cout`/`std::ostringstream`:

```cpp
#include
#include
#include

int main() {
std::vector vec = {1, 2, 3, 4};
auto filtered = vec | lz::filter([](int i) { return i % 2 == 0; }); // == {2, 4}

// To a stream
std::cout << filtered; // prints "2 4" (only works for lz iterables)
lz::format(filtered, std::cout, ", ", "{:02d}"); // prints "02, 04" (only with {fmt} installed or C++20's )
lz::format(filtered, std::cout, ", "); // prints "2, 4"
fmt::print("{}", fmt::join(filtered, ", ")); // prints "2, 4" (only with {fmt} installed)

filtered | lz::format(std::cout, ", "); // prints "2, 4"
filtered | lz::format; // prints "2, 4"
filtered | lz::format(std::cout, ", ", "{:02d}"); // prints "02, 04" (only with {fmt} installed or C++20's )
}
```

# Installation
## Options
The following CMake options are available, all of which are optional:
- `CPP-LAZY_USE_STANDALONE`: Use the standalone version of cpp-lazy. This will not use the library `{fmt}`. Default is `FALSE`
- `CPP-LAZY_LZ_USE_MODULES`: (experimental): Use C++20 modules. Default is `FALSE`
- `CPP-LAZY_DEBUG_ASSERTIONS`: Enable debug assertions. Default is `TRUE` for debug mode, `FALSE` for release.
- `CPP-LAZY_USE_INSTALLED_FMT`: Use the system installed version of `{fmt}`. This will not use the bundled version. Default is `FALSE`. `find_package(fmt REQUIRED CONFIG)` will be used (if `CPP-LAZY_USE_STANDALONE` is `FALSE`) and will try to find `fmt` independently so no `-D fmt_DIR=...` is needed. If for some reason `fmt` cannot be found intrinsically, you can still use `-D fmt_DIR=...` to point to the installed version of `fmt`.
- `CPP-LAZY_INSTALL`: Install cpp-lazy targets and config files. Default is `FALSE`.
- `CPP-LAZY_FMT_DEP_VERSION`: version of `{fmt}` to use. Used if `CPP-LAZY_USE_INSTALLED_FMT` is `TRUE` or `CPP-LAZY_USE_STANDALONE` is `FALSE`. May be empty.

### Using `FetchContent`
The following way is recommended (cpp-lazy version >= 5.0.1). Note that you choose the cpp-lazy-src.zip, and not the source-code.zip/source-code.tar.gz. This prevents you from downloading stuff that you don't need, and thus preventing pollution of the cmake build directory:
```cmake

# Uncomment this line to use the cpp-lazy standalone version or use -D CPP-LAZY_USE_STANDALONE=TRUE
# set(CPP-LAZY_USE_STANDALONE TRUE)

include(FetchContent)
FetchContent_Declare(cpp-lazy
URL https://github.com/Kaaserne/cpp-lazy/releases/download//cpp-lazy-src.zip
# Below is optional
# URL_MD5
# If using CMake >= 3.24, preferably set to TRUE
# DOWNLOAD_EXTRACT_TIMESTAMP
)
FetchContent_MakeAvailable(cpp-lazy)

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} cpp-lazy::cpp-lazy)
```

An alternative ('less' recommended), add to your `CMakeLists.txt` the following:
```cmake
# Uncomment this line to use the cpp-lazy standalone version
# set(CPP-LAZY_USE_STANDALONE TRUE)

include(FetchContent)
FetchContent_Declare(cpp-lazy
GIT_REPOSITORY https://github.com/Kaaserne/cpp-lazy
GIT_TAG ... # Commit hash
# If using CMake >= 3.24, preferably set to TRUE
# DOWNLOAD_EXTRACT_TIMESTAMP
)
FetchContent_MakeAvailable(cpp-lazy)

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} cpp-lazy::cpp-lazy)
```

## Using find_package (after installing)
// TODO

## With xmake
Everything higher than version 7.0.2 is supported. Please note that version 9.0.0 has drastic changes in the API.
```xmake
add_requires("cpp-lazy >=9.0.0")

target("test")
add_packages("cpp-lazy")
```

## Without CMake
### Without `{fmt}`
- Clone the repository
- Specify the include directory to `cpp-lazy/include`.
- Include files as follows:

```cpp
// Important, preprocessor macro 'LZ_STANDALONE' has to be defined already
// or
// #define LZ_STANDALONE
#include
#include

int main() {
std::array arr = {1, 2, 3, 4};
auto result = lz::map(arr, [](int i) { return i + 1; }) | lz::to(); // == {2, 3, 4, 5}
// or
auto result = lz::to(arr | lz::map([](int i) { return i + 1; })); // == {2, 3, 4, 5}
}
```

### With `{fmt}`
- Clone the repository
- Specify the include directory to `cpp-lazy/include` and `fmt/include`.
- Define `FMT_HEADER_ONLY` before including any `lz` files.
- Include files as follows:

```cpp
#define FMT_HEADER_ONLY

#include
#include

int main() {
std::array arr = {1, 2, 3, 4};
auto result = lz::map(arr, [](int i) { return i + 1; }) | lz::to(); // == {2, 3, 4, 5}
// or
auto result = lz::to(arr | lz::map([](int i) { return i + 1; })); // == {2, 3, 4, 5}
}
```

### Using `git clone`
Clone the repository using `git clone https://github.com/Kaaserne/cpp-lazy/` and add to `CMakeLists.txt` the following:
```cmake
add_subdirectory(cpp-lazy)
add_executable(${PROJECT_NAME} main.cpp)

target_link_libraries(${PROJECT_NAME} cpp-lazy::cpp-lazy)
```

# Benchmarks
The time is equal to one iteration. One iteration includes the creation of the iterable and one iteration of that iterable. Compiled with: gcc version 13.3.0.