Awesome
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]
orfmt::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
andfmt/include
. - Define
FMT_HEADER_ONLY
before including anylz
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
<div style="text-align:center"><img src="https://raw.githubusercontent.com/MarcDirven/cpp-lazy/master/bench/benchmarks-iterators-C%2B%2B11.png" /></div>C++14
<div style="text-align:center"><img src="https://raw.githubusercontent.com/MarcDirven/cpp-lazy/master/bench/benchmarks-iterators-C%2B%2B14.png" /></div>C++17
<div style="text-align:center"><img src="https://raw.githubusercontent.com/MarcDirven/cpp-lazy/master/bench/benchmarks-iterators-C%2B%2B17.png" /></div>C++20
<div style="text-align:center"><img src="https://raw.githubusercontent.com/MarcDirven/cpp-lazy/master/bench/benchmarks-iterators-C%2B%2B20.png" /></div>