cdbrkfxrpt / libpipe

Library (or rather, header file) which overloads the pipe operator so that it works like the UNIX pipe operator.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

libpipe

... is a small C++ library implementing the pipe operator syntax. You can use it with any callables you want - functions, lambdas and classes implementing the operator() are all fine.

State

Unfinished. Works so far with callables which aren't overloaded in their argument types, i.e. those taking auto or templated parameters as arguments do not work. Also, the constexpr support isn't fully there yet.

How to build etc.

You will need a C++17 capable compiler. Other than that, libpipe has no dependencies outside the STL and is header only. An aggregate header is provided.

The first step after cloning is then probably to build and run the tests:

mkdir build && cd build
cmake ..
make
./main

Most tests are done via static_assert, so if it builds, that's already a big win. Some tests are just asserts, so if you get no output from running, you're good to go.

Next, you'll want to integrate libpipe into your project. In order to do this, you have to add the include directory to your include path (CMake let's you do this per target via target_include_directories (target_name include_directories), in Make you'd just set the -i compiler flag, what other build systems do I don't know).

Then just do

#include "libpipe.h"

and you're good to go. make_pipeable and operator| will be at your disposal. See usage below.

Usage

There's a few things that work and a few things that don't. Be advised, and don't blame me for the compiler errors. Sorry though.

What it works with

Anything that can be called with one or more arguments, that is:

  • Functions
  • Lambdas
  • Classes implementing operator()

Of course, functions which take no arguments make little sense, but there is another restriction: the types of the arguments must be known at compile time, so template functions do not work and lambdas which take auto arguments also don't. Furthermore, while lambdas can be capturing (as in: [=]() {}, where the [=] captures all before values by copy), but you should be careful with that: the capturing happens when the lambda is first instantiated, not when the pipeable object is made or used. Safe usage is shown below.

By example

To give you an idea how it is used, what works and what doesn't:

//
// functions and lambdas must have known argument types, no templates or auto.
//
constexpr int add_fn(int x, int s) {
  return x + s;
}

// sidenote: lambdas are constexpr by default, in case you didn't know - neato!
auto divide_fn = [](int x, int d) { return x / d; };

//
// classes implementing operator() can be templates themselves (so the argument
// type is encapsulated in the object type later), but must not have
// template/auto arguments either.
//
struct increment_fn
{
  constexpr auto operator()(int t) const noexcept { return t + 1; }
};

template <typename T, REQUIRES(std::is_arithmetic<T>())>
struct multiply_fn
{
  constexpr auto operator()(T t, T f) const noexcept { return t * f; }
};

struct modulo_fn
{
  constexpr auto operator()(int t, int d) const noexcept { return t % d; }
};

//
// callables are set up to be pipeable in the following way. you can of course
// build this into the type via `operator pipeable<decltype(*this)>` or
// something like that, i haven't actually tried.
//
constexpr auto add       = make_pipeable(add_fn);
constexpr auto divide    = make_pipeable(divide_fn);
constexpr auto increment = make_pipeable(increment_fn{});
constexpr auto multiply  = make_pipeable(multiply_fn<int>{});
constexpr auto modulo    = make_pipeable(modulo_fn{});

// pipe-evaluating a unary function with an argument
const auto fortytwo = 41 | increment;
static_assert(fortytwo == 42);

// pipe-evaluating a non-unary function with an argument and a parameter
const auto alsofortytwo = 21 | multiply(2);
static_assert(alsofortytwo == 42);

// chaining pipeables
const auto anotherfortytwo =
  19 | increment | multiply(4) | add(4) | divide(2);
static_assert(anotherfortytwo == 42);

const auto againfortytwo = 21 | add(21);
static_assert(againfortytwo == 42);

//
// composing pipes to receive a new pipeable object (which can of course also
// be used as a function). this does not work at compile time yet - i'm working
// on it.
//
auto alwaysfortytwo =
  multiply(10) | add(4) | modulo(10) | multiply(10) | increment | increment;

assert(alwaysfortytwo(1983447) == 42);

As mentioned above, lambdas can be capturing, but you must be careful with it. Typically, avoid:

auto some_lambda = [&m](int x) noexcept { return m * x; };
// ... a bunch of code
auto some_lambda_pipeable = make_pipeable(some_lambda);

Remember: the capture happens when you created the lambda, which means that things can already have gone out of scope by the time you're trying to use them in your pipeable. It's not like this is not the case with lambdas anyway, I am just mentioning it because libpipe creates a smoke screen around the lambdas and the compiler errors you currently get are less than beautiful to work with as it stands.

Safe uses for capturing lambdas are:

// capture by value
auto some_lambda = [=m](int x) noexcept { return m * x; };

// using a function to generate a pipeable object
auto multiply(int m) noexcept {
  return make_pipeable([m](int x) noexcept { return m * x; });
}

So much for the things that do work. The following wouldn't work:

// auto argument must be deduced, compiler sees some_lambda as overloaded
auto some_lambda = [](auto x) { return x * x; }

// functor has templated call arguments, compiler sees it as overloaded
struct some_func
{
  template <typename T>
  constexpr auto operator(T t) { return t * t; }
};

// functor overloads operator()
struct some_other_func
{
  constexpr auto operator(int i) { return i * i; }
  constexpr auto operator(float i) { return i * i; }
};

The return type can be auto, though. There's no harm in that. The arguments, however, must be known at compile time, which means that the function must not be overloaded (or seen as overloaded due to type deduction by the compiler).

Acknowledgements

My main source of inspiration for this was Eric Niebler's talk at CppCon 2015 and, of course, his range-v3 library as well as the (currently not yet merged to head) feat-192-nviews branch of the DASH project.

Furthermore, I found this great blog post by Paul Fultz II, which ended up being my most important influence for the implementation.

About

Library (or rather, header file) which overloads the pipe operator so that it works like the UNIX pipe operator.

License:Apache License 2.0


Languages

Language:C++ 75.8%Language:CMake 18.9%Language:C 5.2%Language:Vim Script 0.1%