Skip to content

Kaaserne/cpp-lazy

Repository files navigation

Build status License: MIT CodeQL

Examples can be found here. Installation can be found here.

cpp-lazy

Cpp-lazy is a fast and easy lazy evaluation library for C++11/14/17/20. This is a fast library because the library does not allocate any memory. Moreover, the iterators are random-access where possible. Therefore operations, for example std::distance, are an O(1) operation by adding a std::random_access_iterator_tag if possible. Furthermore, the view object has many std::execution::* overloads. This library uses one (optional) dependency: the library {fmt}, more of which can be found out in the installation section. Example:

#include <Lz/Map.hpp>

int main() {
  std::array<int, 4> arr = {1, 2, 3, 4};
  std::string result = lz::map(arr, [](int i) { return i + 1; }).toString(" "); // == "2 3 4 5"
}

Features

  • C++11/14/17/20; C++20 concept support; C++17 execution support (std::execution::par/std::execution::seq etc...)
  • Easy print using std::cout << [lz::IteratorView] or fmt::print("{}", [lz::IteratorView])
  • Compatible with old(er) compiler versions; at least gcc versions => 4.8 & clang => 5.0.0 (previous versions have not been checked, so I'd say at least a compiler with C++11 support).
  • Tested with -Wpedantic -Wextra -Wall -Wshadow -Wno-unused-function -Werror -Wconversion and /WX for MSVC
  • One optional dependency ({fmt})
  • std::format compatible
  • STL compatible
  • Little overhead
  • Any compiler with at least C++11 support is suitable
  • Easy installation
  • Clear Examples
  • Readable, using method chaining

What is lazy and why would I use it?

Lazy evaluation is an evaluation strategy which holds the evaluation of an expression until its value is needed. In this library, all the iterators are lazy evaluated. Suppose you want to have a sequence of n random numbers. You could write a for loop:

std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution dist(0, 32);

for (int i = 0; i < n; i++) {
 std::cout << dist(gen); // prints a random number n times, between [0, 32]
}

This is actually exactly the same as:

// If standalone:
std::cout << lz::random(0, 32, n);

// If with fmt:
fmt::print("{}", lz::random(0, 32, n));

Both methods do not allocate any memory but the second example is a much more convenient way of writing the same thing. Now what if you wanted to do eager evaluation? Well then you could do this:

std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution dist(0, 32);
std::vector<int> randomNumbers;
std::generate(randomNumbers.begin(), randomNumbers.end(), [&dist, &gen]{ return dist(gen); });

That is pretty verbose. Instead, try this for change:

std::vector<int> randomNumbers = lz::random(0, 32, n).toVector();

I want to search if the sequence of random numbers contain 6.

In 'regular' C++ code that would be:

std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution dist(0, 32);

for (int i = 0; i < n; i++) {
 if (gen(dist)) == 6) {
  // do something
 }
}

In C++ using this library and because all iterators in this library are STL compatible, we could simply use std::find:

auto random = lz::random(0, 32, n);
if (std::find(random.begin(), random.end(), 6) != random.end()) {
 // do something
}

// or

if (lz::contains(random, 6)) {
  // do something
}

So by using this lazy method, we 'pretend' it's a container, while it actually is not. Therefore it does not allocate any memory and has very little overhead.

Writing loops yourself

I understand where you're coming from. You may think it's more readable. But the chances of getting bugs are bigger because you will have to write the whole loop yourself. On average about 15 – 50 errors per 1000 lines of delivered code contain bugs. While this library does all the looping for you and is thoroughly tested using catch2. The lz::random for-loop equivalent is quite trivial to write yourself, but you may want to look at lz::concat.

Installation

With xmake

Everything higher than version 7.0.2 is supported.

add_requires("cpp-lazy >=7.0.2")

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:
// Important, preprocessor macro 'LZ_STANDALONE' has to be defined already
#include <Lz/Map.hpp>

int main() {
  std::array<int, 4> arr = {1, 2, 3, 4};
  std::string result = lz::map(arr, [](int i) { return i + 1; }).toString(" "); // == "1 2 3 4"
}

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:
#define FMT_HEADER_ONLY
#include <Lz/Map.hpp>

int main() {
  std::array<int, 4> arr = {1, 2, 3, 4};
  std::string result = lz::map(arr, [](int i) { return i + 1; }).toString(" "); // == "2 3 4 5"
}

With CMake

If you want to use the standalone version, then use the CMake option -D CPP-LAZY_USE_STANDALONE=ON or set(CPP-LAZY_USE_STANDALONE TRUE). This also prevents the cloning of the library {fmt}.

Using FetchContent

Add to your CMakeLists.txt the following:

# 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/MarcDirven/cpp-lazy
        GIT_TAG ... # Commit hash
        # If using CMake >= 3.24, preferably set <bool> to TRUE
        # DOWNLOAD_EXTRACT_TIMESTAMP <bool>
)
FetchContent_MakeAvailable(cpp-lazy)

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

However, 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):

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

include(FetchContent)
FetchContent_Declare(cpp-lazy
        URL https://github.com/MarcDirven/cpp-lazy/releases/download/<TAG_HERE E.G. 5.0.1>/cpp-lazy-src.zip
        # Below is optional
        # URL_MD5 <MD5 HASH OF cpp-lazy-src.zip>
        # If using CMake >= 3.24, preferably set <bool> to TRUE
        # DOWNLOAD_EXTRACT_TIMESTAMP <bool>
)
FetchContent_MakeAvailable(cpp-lazy)

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

This also prevents you from downloading stuff that you don't need, and thus preventing pollution of the cmake build directory.

Using git clone

Clone the repository using git clone https://github.com/MarcDirven/cpp-lazy/ and add to CMakeLists.txt the following:

add_subdirectory(cpp-lazy)
add_executable(${PROJECT_NAME} main.cpp)

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

Or add cpp-lazy/include to the additional include directories in e.g. Visual Studio.

Including

#include <Lz/Lz.hpp> // or e.g. #include <Lz/Filter.hpp>

int main() {
  // use e.g. lz::filter
}

Benchmarks cpp-lazy

The time is equal to one iteration. Compiled with: winlibs-x86_64-posix-seh-gcc-10.2.1-snapshot20200912-mingw-w64-7.0.0-r1

C++11

C++14

C++17

C++20