Look ma' no metaprogramming!
In my previous blog post, I discussed creating the ‘in’ operator using the questionable infix operator. However, the more interesting part of the blog post was the implementation of find_iterator
:
FIT_STATIC_LAMBDA_FUNCTION(find_iterator) = fit::conditional(
[](const std::string& s, const auto& x)
{
auto index = s.find(x);
if (index == std::string::npos) return s.end();
else return s.begin() + index;
},
[](const auto& r, const auto& x) -> decltype(r.find(x))
{
return r.find(x);
},
[](const auto& r, const auto& x)
{
using std::begin;
using std::end;
return std::find(begin(r), end(r), x);
}
);
So this function is “smarter” than just plain std::find
as it will take advantage of the intrinsic find
if available. So by utilizing conditional overloading and trailing decltype
, we can solve this problem quite simply without having to resort to template metaprogramming. Basically, if the first function can’t be called it will try to call the next, until it can find a function that can be called. Here we use the fit::conditional
adaptor to accomplish this. Let’s look at what more we can do using the Fit library without touching template metaprogramming.
Pretty print
Say we would like to write a print
function that can print not only using cout
but can also print the values in ranges. We could write something like this:
FIT_STATIC_LAMBDA_FUNCTION(print) = fit::conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range)
{
for(const auto& x:range) std::cout << x << std::endl;
}
);
So the -> decltype(std::cout << x, void())
will only make the function callable if std::cout << x
is callable. Then the void()
is used return void
from the function. We can constrain the second overload as well, but we will need some helper function in order to call std::begin
and std::end
using adl lookup:
namespace adl {
using std::begin;
using std::end;
template<class R>
auto adl_begin(R&& r) -> FIT_RETURNS(begin(r));
template<class R>
auto adl_end(R&& r) -> FIT_RETURNS(end(r));
}
FIT_STATIC_LAMBDA_FUNCTION(print) = fit::conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range) -> decltype(std::cout << *adl::adl_begin(range), void())
{
for(const auto& x:range) std::cout << x << std::endl;
}
);
Print tuples
We could extend this to printing tuples as well, but we need a for_each_tuple
to call the function for each element. Fortunately, we don’t need to resort to variadic templates. We can use fit::each_arg
to call each element and then use fit::fuse
to convert tuple elements to function arguments. We also use fit::capture
to capture f
for the first argument of fit::each_arg
.
FIT_STATIC_LAMBDA_FUNCTION(for_each_tuple) = [](auto&& tuple, auto f) FIT_RETURNS
(fit::fuse(fit::capture(f)(fit::each_arg))(tuple));
So now we can add an overload for tuples:
FIT_STATIC_LAMBDA_FUNCTION(print) = fit::conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range) -> decltype(std::cout << *adl::adl_begin(range), void())
{
for(const auto& x:range) std::cout << x << std::endl;
},
[](const auto& tuple) -> decltype(for_each_tuple(tuple, fit::identity), void())
{
return for_each_tuple(tuple, [](const auto& x)
{
std::cout << x << std::endl;
});
}
);
Since we can’t use a lambda inside of decltype
we just put fit::identity
instead.
Recursive print
Even though we are using lambdas, we can easily make this recursive using fit::fix
. This implements a fix point combinator, which passes the function(ie itself) in as the first argument, so we could write this:
FIT_STATIC_LAMBDA_FUNCTION(print) = fit::fix(fit::conditional(
[](auto, const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void())
{
for(const auto& x:range) self(x);
},
[](auto self, const auto& tuple) -> decltype(for_each_tuple(tuple, self), void())
{
return for_each_tuple(tuple, self);
}
));
Variadic print
So, in python, the print
function can be passed multiple parameters. Once again, no metaprogramming needed for this. We can use fit::each_arg
to call the print function on each argument passed in. We will rename the single argument print
to simple_print
, and then use that function for print
:
FIT_STATIC_LAMBDA_FUNCTION(simple_print) = fit::fix(fit::conditional(
[](auto, const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void())
{
for(const auto& x:range) self(x);
},
[](auto self, const auto& tuple) -> decltype(for_each_tuple(tuple, self), void())
{
return for_each_tuple(tuple, self);
}
));
const constexpr auto print = fit::capture(simple_print)(fit::each_arg);