In functional programming, functions are objects and can be processed like objects. With Boost.Phoenix, it is possible for a function to return another function as a result. It is also possible to pass a function as a parameter to another function. Because functions are objects, it's possible to distinguish between instantiation and execution. Accessing a function isn't equal to executing it.
Boost.Phoenix supports functional programming with function objects: Functions are objects based on classes which overload the operator(). That way function objects behave like other objects in c++. For example, they can be copied and stored in a container. However, they also behave like functions because they can be called.
1. phoenix
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> bool is_odd(int i) { return i % 2 == 1; } int main() { std::vector<int> v{1, 2, 3, 4, 5}; std::cout << std::count_if(v.begin(), v.end(), is_odd) << std::endl; auto lambda = [](int i){ return i % 2 == 1; }; std::cout << std::count_if(v.begin(), v.end(), lambda) << std::endl; using namespace boost::phoenix::placeholders; auto phoenix = arg1 % 2 == 1; std::cout << std::count_if(v.begin(), v.end(), phoenix) << std::endl;
std::vector<long> v2;
v2.insert(v2.begin(), v.begin(), v.end());
auto phoenix = arg1 % 2 == 1;
std::cout << std::count_if(v.begin(), v.end(), phoenix) << '
';
std::cout << std::count_if(v2.begin(), v2.end(), phoenix) << '
';
return 0; }
The Phoenix function differs from free-standing and lambda functions because it has no frame. While the other two functions have a function header with a signature, the Phoenix function seems to consist of a function body only. The crucial component of the Phoenix function is boost::phoenix::placeholders::arg1. arg1 is a global instance of a function object.
arg1 is used to define an unary function. The expression arg1 % 2 == 1
creates a new function that expects one parameter. The function isn’t executed immediately but stored in phoenix. phoenix is passed to std::count_if()
which calls the predicate for every number in v.
Phoenix function parameters have no types. The lambda function lambda expects a parameter of type int. The Phoenix function phoenix will accept any type that the modulo operator can handle. Think of Phoenix functions as function templates. Like function templates, Phoenix functions can accept any type.
You can think of Phoenix functions as C++ code that isn’t executed immediately. The Phoenix function in example above looks like a condition that uses multiple logical and arithmetic operators. However, the condition isn’t executed immediately. It is only executed when it is accessed from within std::count_if()
. The access in std::count_if()
is a normal function call.
2. boost::phoenix::val()
#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
std::vector<int> v{1, 2, 3, 4, 5};
using namespace boost::phoenix;
using namespace boost::phoenix::placeholders;
auto phoenix = arg1 > val(2) && arg1 % val(2) == val(1);
std::cout << std::count_if(v.begin(), v.end(), phoenix) << std::endl;
std::cout << arg1(1, 2, 3, 4, 5) << std::endl;
auto v = boost::phoenix::val(2);
std::cout << v() << std::endl;
return 0; }
boost::phoenix::val() returns a function object initialized with the values passed to boost::phoenix::val(). The acutal type of the function object dosen't matter.
arg1 is an instance of a function object. It can be used directly and called like a function. You can pass as many parameters as you like - arg1 returns the first one.
val() is a function to create an instance of a function object. The function object is initialized with the value passed as a parameter. Is the instance is accessed like a function, the value is returned.
3. create your own phoenix functions
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> struct is_odd_impl { typedef bool result_type; template <typename T> bool operator()(T t) const { return t % 2 == 1; } }; boost::phoenix::function<is_odd_impl> is_odd; int main() { std::vector<int> v{1, 2, 3, 4, 5}; using namespace boost::phoenix::placeholders; std::cout << std::count_if(v.begin(), v.end(), is_odd(arg1)) << std::endl; return 0; }
Class should overload the operator operator(): when an odd number is passed in, the operator returns true. Please note that you must define the type result_type. Boost.Phoenix uses it to detect the type of the return value of the operator operator().
4. transform
#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>
bool is_odd_function(int i) { return i % 2 == 1; }
BOOST_PHOENIX_ADAPT_FUNCTION(bool, is_odd, is_odd_function, 1)
bool is_odd(int i) {
return i % 2 == 1;
}
int main()
{
std::vector<int> v{1, 2, 3, 4, 5};
using namespace boost::phoenix;
using namespace boost::phoenix::placeholders;
std::cout << std::count_if(v.begin(), v.end(), is_odd(arg1)) << std::endl;
std::cout << std::count_if(v.begin(), v.end(), bind(is_odd, arg1)) << std::endl;
return 0; }
Use the macro BOOST_PHOENIX_ADAPT_FUNCTION to turn a free-standing function into a Phoenix function. Pass the type of the return value, the name of the Phoenix function to define, the name of the free-standing function, and the number of parameters to the macro.