Embrace (post) modern C++
Alloy solves the problem of run-time manipulation of heterogeneous values with more flexibility and expressive power than the tools provided by the standard library.
#include "alloy.hpp"
#include <iostream>
#include <string>
#include <utility>
int main() {
// suppose you have some data
auto data = alloy::capture("Hello", ' ', "World" , '!');
// and means to consume it
auto print = [](auto&&... data) {
// we'll simply print to the standard output in this example
(std::cout << ... << std::forward<decltype(data)>(data)) << std::endl;
};
// all you need is to connect the ends
data >> print; // Hello World!
// just as easily you can build data pipelines
auto predicate = [](auto x) {
return std::string{x} != "!";
};
data >> alloy::filter(predicate) >> print; // Hello World
// with Alloy you can kiss `std::apply` goodbye
alloy::unpack(std::make_tuple(3, '.', "14")) >> print; // 3.14
// you can even iterate through tuples in a regular for-loop
auto tup = std::make_tuple(3, '.', "14");
for(std::size_t i = 0; i < std::tuple_size<decltype(tup)>{}; ++i)
alloy::unpack(tup) >> alloy::at(i) >> alloy::prepend(i, ": ") >> print;
// 0: 3
// 1: .
// 2: 14
// `std::visit` is also a thing of the past
using var = std::variant<int, char, std::string>;
var i = 3;
var c = '.';
var s = "14";
alloy::unpack(i, c, s) >> print; // 3.14
// while you are at it, why not mixing tuples and variants together?
alloy::unpack(std::make_tuple("pi", '='), i, c, s) >> print; // pi=3.14
// tuples and variants are too mainstream?
// not a problem, you can also provide your very own custom data sources
auto produce = [](auto consume) {
return consume("Hello", ' ', "World");
};
// the following are all equivalent and print Hello World
alloy::source{produce} >> print;
produce >> alloy::sink{print};
alloy::source{produce} >> alloy::sink{print};
// and your very own streams too
auto process = [](auto const& sink) {
return [&sink](auto hello, auto _, auto world) {
return sink(hello, _, "brave", _, "new", _, world, '!');
};
};
produce >> alloy::stream{process} >> print; // Hello brave new World!
// embrace (post) modern C++
auto wrap = [](auto const& sink) {
return [&sink](auto word) {
return sink('(', word, ')');
};
};
alloy::forward("post") >> alloy::stream{wrap}
>> alloy::prepend("embrace", ' ')
>> alloy::append(' ', "modern C++") >> print;
}
Have you ever wished std::tuple
provided an overload for operator []
just
likestd::vector
and std::map
do?
As you might have guessed, it's actually impossible to overload the subscript operator for tuples, but the reason why might not be obvious at first.
An example is worth a thousand words, so can you guess the type of x
below?
auto x = std::make_tuple(1, 1.0, '1', "one")[std::rand() % 4];
The type of x
depends on a run-time value and therefore may not be deduced by
the compiler, which is thus unable to compile such a program to begin with.
Fine, as long as we are able to, say, filter the elements in a std::tuple
based on a predicate, we should be able to tackle most real world problems.
That should be easy right?
Nope.
Can you deduce the type of x
this time?
template<typename Tuple, typename Predicate>
decltype(auto) filter(Tuple&&, Predicate&&);
auto x = filter(std::make_tuple(1, 1.0, '1', "one"), [](auto&&) {
return std::rand() % 2;
});
Heck, the predicate doesn't even depend on the elements themselves, but still we are unable to deduce the return type!
Alright, forget about returning values, let's forward results to a callback function instead.
template<typename Tuple, typename Predicate, typename Callback>
void filter(Tuple&&, Predicate&&, Callback&&);
auto predicate = [](auto&&) {
return std::rand() % 2;
};
auto callback = [](auto&&... /*args*/) { /* ... */ };
filter(std::make_tuple(1, 1.0, '1', "one"), predicate, callback);
That was easy after all... or was it? Let us not forget that we still need to
implement filter
.
How can the standard library help us get there, you might have asked yourself, and the answer is quite simple in fact: It can't really.
Sure std::apply
can help us extract the elements out of the tuple, but that's
about all the standard library can do for us, from then on we are on our own.
template<typename Tuple, typename Predicate, typename Callback>
void filter(Tuple&& tuple, Predicate&& predicate, Callback&& callback) {
constexpr auto impl = [&predicate, &callback](auto&&... elements) {
// TODO: do the heavy lifting :(
};
std::apply(std::forward<Tuple>(tuple), impl);
}
We don't want to keep reinventing the wheel, we want to solve real problems, but for that we need the building blocks.
auto tuple = std::make_tuple(1, 1.0, '1', "one");
auto predicate = [](auto&&) {
return std::rand() % 2;
};
auto callback = [](auto&&... /*args*/) { /* ... */ };
alloy::unpack(tuple) >> alloy::filter(predicate) >> callback;
We need Alloy.
- Download alloy.hpp
# include </path/to/alloy.hpp>
- Embrace (post) modern C++
Alloy requires C++17 and is known to work on GCC and Clang.
This project is licensed under the MIT.