functional.h - JonathanCline/JCLib GitHub Wiki
Documentation and examples for using the jclib/functional.h
header.
-
operator
- Any struct or class that inherits fromjc::operator_tag
and has anoperator()
overload defined.
The goal of the functional.h
header and its contents is to provide a system for composing function objects together to reduce the amount of code it takes to write common lambdas and to simplify the usage of C++ code that uses a user-provided function (ie. std::find_if
).
Here is an example of the systems provided in action:
bool has_number(const std::vector<std::unique_ptr<int>>& vec, int number)
{
// Creates a new function object that is equivalent to (*x == number)
const auto cmp = jc::dereference | jc::equals & number;
// Call find_if using cmp as the predicate
return std::find_if(vec.begin(), vec.end(), cmp);
};
Piping refers to invoking an operator
with a value or with the results from calling another operator
.
Here is an example of argument piping :
// Here we are piping the variable "a" into the negation operator.
int a = 5;
// This is the same as "-a"
int na = a | jc::negate; // "na" is now equal to -5
// Here we are piping the variable "b" into the negation operator and then piping that result into the negation operator again.
int b = 9;
// This is the same as "-(-b)"
int nb = b | jc::negate | jc::negate; // b is now equal to 5
Piping will only work if you are piping a value to an operator
that actually accepts said value.
int a = 6;
// This will not work as jc::plus takes 2 arguments
int sum = a | jc::plus; // compilation error!
An operator
can have arguments "bound" to them to create a new operator
with one argument now filled by the "bound" value.
This is useful for common operator
usage patterns.
Here is an example of argument binding:
// This is the equivalent of jc::plus(x, 1)
auto inc = jc::plus & 1;
// "inc" is a new operator that only takes 1 argument. It still calls jc::plus but now one of the jc::plus arguments will be 1
int a = 0;
int b = a | inc; // b is equal to 1
int c = a | inc | inc; // c is equal to 2
The side that the argument is bound on is important. See the following code snippet:
// This operator will return true if a value is greater than 4
auto a = jc::greater & 4; // a > 4
// This operator will return true if 4 is greater than a value
auto b = 4 & jc::greater; // 4 > a
// The results will be different!!!
int n = 8;
auto aresult = n | a;
auto bresult = n | b;
An operator
can be composed with another operator
to form a new operator
using the regular piping semantics |
.
This new operator
will pipe the results from the left hand operator
into the right hand operator
, allowing the user to generate functionality by composing existing operator
s together.
Here is an small example of composition:
// This will check if a number plus 2 is equal to 4
auto f = (jc::plus & 2) | jc::equals & 4; // (a + 2) == 4
int n = 2;
auto b = n | f; // b will be "true" as 2 + 2 is equal to 4
Argument packs allow for an operator
with multiple arguments to be invoked with a single value. In order to prevent accidentally invoking operator
s incorrectly, this uses a special type that is essentially an invariant of std::tuple
- jc::argpack
.
jc::pack()
is a function that can be used to make an argument pack from a set of arguments.
jc::pack(2, 2); // creates an argument pack containing (2, 2)
int n = 0;
jc::pack(n, 2); // creates an argument pack containing (&n, 2);
Here is an example of using argument packs to call jc::plus
auto sum = jc::pack(2, 2) | jc::plus; // 2 + 2 = 4 !
When created, argument packs will not make copies of given arguments! This means that if you create an argument pack with a variable, the argument pack will hold a reference to it. If the variable being referenced goes out of scope, the argument pack is now invalid and using it will result in undefined behavior.
auto foo()
{
int n = 0;
return jc::pack(n, 2); // THIS IS NOT GOOD!!!!! "n" is a local variable and will go out of scope!!!
};
auto p = foo();
auto s = p | jc::plus; // Undefined Behavior as "p" contains a reference to a variable that went out of scope ("n")
If you have a type that has a jc::get_ftor
specialization it can be turned into an argument pack using the jc::repack
operator
. Anything in the standard library that works with std::get
works by default (for example: std::tuple
and std::pair
).
An argument pack can also be used with jc::repack
, returning the same argument pack.
// Create a pair containing (2, 2)
auto args = std::pair<int, int>{ 2, 2 };
// Turn the pair into an argument pack and call plus with it
auto sum = args | jc::repack | jc::plus; // sum is equal to 4
// Calling repack multiple times is ok and just keeps returning the same argument pack
auto sum2 = args | jc::repack | jc::repack | jc::plus; // same as the previous line of code
// NOTICE: This will fail to compile! The only way to call an operator with multiple arguments using a single value is with an actual jc::argpack
auto bad = args | jc::plus; // error! jc::plus takes 2 arguments, only 1 was provided