Hi all! In this post I explore the range-v3 library, not to be confused with the native ranges library.
Functional
Create Pipes!
Create your own custom pipeable
lambdas in an easy way, just create them as an argument of rg::make_pipeable
, and boom. Done. That’s too cool!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <range/v3/all.hpp>
#include <fmt/ranges.h>
namespace rg = ranges;
int main()
{
// Raise to the second power
auto pow = rg::make_pipeable([](auto&& e){ return e*e; });
// Take the remainder with 5
auto mod = rg::make_pipeable([](auto&& e){ return e%5; });
// Perform the operations
auto num{8 | pow | mod};
// Print result
fmt::print("{}",num);
return 0;
}
output: 4
Compose functions
This is pretty cool, rg::compose
creates a function that is a composition of both passed as a parameter. In this example, one lambda picks the second element of the vector v
, and, the second one raises to the second power. To merge this behaviours rg::compose
, takes the behaviours in reverse order of application, as shown in the example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>
#include <range/v3/all.hpp>
#include <fmt/ranges.h>
namespace rg = ranges;
int main()
{
std::vector<int> v{1,2,3,4,5};
auto second = [](auto&& v){ return v.at(1); };
auto pow = [](auto&& e){ return e*e; };
auto second_pow = rg::compose(pow,second);
fmt::print("{}",second_pow(v));
return 0;
}
output: 4
Parameter type checks
Pretty useful for concept
validation, when used with static_assetions
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <concepts>
#include <cstdint>
#include <type_traits>
#include <range/v3/all.hpp>
#include <fmt/ranges.h>
namespace rg = ranges;
template<typename T>
concept Integral = std::is_integral_v<T>;
int main()
{
auto sum = []<Integral I>(I&& e){ return e+e; };
fmt::print("{}\n",rg::is_invocable_v<decltype(sum),int8_t>);
fmt::print("{}\n",rg::is_invocable_v<decltype(sum),int16_t>);
fmt::print("{}\n",rg::is_invocable_v<decltype(sum),int32_t>);
fmt::print("{}\n",rg::is_invocable_v<decltype(sum),int64_t>);
fmt::print("{}\n",rg::is_invocable_v<decltype(sum),float>);
fmt::print("{}\n",rg::is_invocable_v<decltype(sum),double>);
fmt::print("{}\n",rg::is_invocable_v<decltype(sum),long double>);
return 0;
}
output: true true true true false false false
Algorithms
Sum the elements of a container
This task is quite easy with ranges, with the rg::accumulate
method, the first parameter is the range to reverse, rv::all(v)
encapsulates vector v
into a range; the second parameter is added to the final sum, thus, rg::accumulate(rv::all(v),10)
yields the value 16
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>
#include <iostream>
#include <range/v3/all.hpp>
#include <fmt/core.h>
namespace rg = ranges;
namespace rv = ranges::views;
int main()
{
std::vector<int> v{1,2,3};
auto sum = rg::accumulate(rv::all(v),0);
fmt::print("{}\n", sum);
return 0;
}
Output: 6
Reverse a container
This is pretty straightforward, I have a thing for the |=
operator, looks cool.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>
#include <iostream>
#include <concepts>
#include <range/v3/all.hpp>
#include <fmt/ranges.h>
namespace ra = ranges::actions;
int main()
{
std::vector<int> v{1,2,3};
v |= ra::reverse;
fmt::print("{}\n", v);
return 0;
}
Output: {3, 2, 1}
Split a container in n chunks
Splits a container into n
sub containers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>
#include <iostream>
#include <range/v3/all.hpp>
#include <fmt/ranges.h>
namespace rg = ranges;
namespace rv = ranges::views;
int main()
{
std::vector<int> v{1,2,3,4,5};
auto chunks {rv::chunk(rv::all(v),2) | rg::to<std::vector<std::vector<int>>>};
rg::for_each(chunks,[](auto&& c){ fmt::print("{}\n", c); });
return 0;
}
output: {1, 2} {3, 4} {5}
Intersperse an element
Inserts an element in-between every element in the container.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>
#include <iostream>
#include <range/v3/all.hpp>
#include <fmt/ranges.h>
namespace rg = ranges;
namespace rv = ranges::views;
int main()
{
std::vector<int> v{1,2,3,4,5};
auto interspersed {rv::intersperse(rv::all(v),30) | rg::to<std::vector>};
fmt::print("{}\n", interspersed);
return 0;
}
output: {1, 30, 2, 30, 3, 30, 4, 30, 5}
Find an element
Searches for an element in a very concise way.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <vector>
#include <range/v3/all.hpp>
#include <fmt/ranges.h>
namespace rg = ranges;
int main()
{
std::vector<int> v{1,2,3,4,5};
auto search {rg::find(v,3)};
(search != rg::end(v))? fmt::print("{}\n", *search) : fmt::print("Not item in vector");
return 0;
}
output: 3
Get a reference to an element
Similar to std::reference_wrapper, but… shorter!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <vector>
#include <range/v3/all.hpp>
#include <fmt/ranges.h>
namespace rg = ranges;
int main()
{
std::vector<int> v{1,2,3,4,5};
auto ref {rg::ref(v.at(2))};
ref.get() = 8;
fmt::print("{}",v);
return 0;
}
output: {1, 2, 8, 4, 5}