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;
    }
);

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);