PrevUpHomeNext

Boost.Phoenix-style let()

Boost.Phoenix has a thing called let(). It introduces named reusable values that are usable in subsequent expressions. This example is something simliar, though not exactly like Phoenix's version. In Phoenix, a let placeholder is only evaluated once, whereas the example below does something more like macro substitution; each let-placeholder is replaced with its initializing expression everywhere it is used.

This example uses C++17's if constexpr (), simply because it makes the example shorter and easier to digest. The if constexpr () bits are not strictly necessary.

#include <boost/yap/yap.hpp>

#include <boost/hana/map.hpp>
#include <boost/hana/at_key.hpp>
#include <boost/hana/contains.hpp>
#include <boost/hana/keys.hpp>

#include <vector>
#include <iostream>


// Here, we introduce special let-placeholders, so we can use them along side
// the normal YAP placeholders without getting them confused.
template<long long I>
struct let_placeholder : boost::hana::llong<I>
{
};

// Replaces each let-terminal with the expression with which it was
// initialized in let().  So in 'let(_a = foo)[ _a + 1 ]', this transform will
// be used on '_a + 1' to replace '_a' with 'foo'.  The map_ member holds the
// mapping of let-placeholders to their initializers.
template<typename ExprMap>
struct let_terminal_transform
{
    // This matches only let-placeholders.  For each one matched, we look up
    // its initializer in map_ and return it.
    template<long long I>
    auto operator()(
        boost::yap::expr_tag<boost::yap::expr_kind::terminal>,
        let_placeholder<I> i)
    {
        // If we have an entry in map_ for this placeholder, return the value
        // of the entry.  Otherwise, pass i through as a terminal.
        if constexpr (boost::hana::contains(
                          decltype(boost::hana::keys(map_))(),
                          boost::hana::llong_c<I>)) {
            return map_[boost::hana::llong_c<I>];
        } else {
            return boost::yap::make_terminal(i);
        }
    }

    ExprMap map_;
};

// As you can see below, let() is an eager function; this template is used for
// its return values.  It contains the mapping from let-placeholders to
// initializer expressions used to transform the expression inside '[]' after
// a let()'.  It also has an operator[]() member function that takes the
// expression inside '[]' and returns a version of it with the
// let-placeholders replaced.
template<typename ExprMap>
struct let_result
{
    template<typename Expr>
    auto operator[](Expr && expr)
    {
        return boost::yap::transform(
            std::forward<Expr>(expr), let_terminal_transform<ExprMap>{map_});
    }

    ExprMap map_;
};

// Processes the expressions passed to let() one at a time, adding each one to
// a Hana map of hana::llong<>s to YAP expressions.
template<typename Map, typename Expr, typename... Exprs>
auto let_impl(Map && map, Expr && expr, Exprs &&... exprs)
{
    static_assert(
        Expr::kind == boost::yap::expr_kind::assign,
        "Expressions passed to let() must be of the form placeholder = Expression");
    if constexpr (sizeof...(Exprs) == 0) {
        using I = typename std::remove_reference<decltype(
            boost::yap::value(boost::yap::left(expr)))>::type;
        auto const i = boost::hana::llong_c<I::value>;
        using map_t = decltype(boost::hana::insert(
            map, boost::hana::make_pair(i, boost::yap::right(expr))));
        return let_result<map_t>{boost::hana::insert(
            map, boost::hana::make_pair(i, boost::yap::right(expr)))};
    } else {
        using I = typename std::remove_reference<decltype(
            boost::yap::value(boost::yap::left(expr)))>::type;
        auto const i = boost::hana::llong_c<I::value>;
        return let_impl(
            boost::hana::insert(
                map, boost::hana::make_pair(i, boost::yap::right(expr))),
            std::forward<Exprs>(exprs)...);
    }
}

// Takes N > 0 expressions of the form 'placeholder = expr', and returns an
// object with an overloaded operator[]().
template<typename Expr, typename... Exprs>
auto let(Expr && expr, Exprs &&... exprs)
{
    return let_impl(
        boost::hana::make_map(),
        std::forward<Expr>(expr),
        std::forward<Exprs>(exprs)...);
}

int main()
{
    // Some handy terminals -- the _a and _b let-placeholders and std::cout as
    // a YAP terminal.
    boost::yap::expression<
        boost::yap::expr_kind::terminal,
        boost::hana::tuple<let_placeholder<0>>> const _a;
    boost::yap::expression<
        boost::yap::expr_kind::terminal,
        boost::hana::tuple<let_placeholder<1>>> const _b;
    auto const cout = boost::yap::make_terminal(std::cout);

    using namespace boost::yap::literals;

    {
        auto expr = let(_a = 2)[_a + 1];
        assert(boost::yap::evaluate(expr) == 3);
    }

    {
        auto expr = let(_a = 123, _b = 456)[_a + _b];
        assert(boost::yap::evaluate(expr) == 123 + 456);
    }

    // This prints out "0 0", because 'i' is passed as an lvalue, so its
    // decrement is visible outside the let expression.
    {
        int i = 1;

        boost::yap::evaluate(let(_a = 1_p)[cout << --_a << ' '], i);

        std::cout << i << std::endl;
    }

    // Prints "Hello, World" due to let()'s scoping rules.
    {
        boost::yap::evaluate(
            let(_a = 1_p, _b = 2_p)
            [
                // _a here is an int: 1

                let(_a = 3_p) // hides the outer _a
                [
                    cout << _a << _b // prints "Hello, World"
                ]
            ],
            1, " World", "Hello,"
        );
    }

    std::cout << "\n";

    // Due to the macro-substitution style that this example uses, this prints
    // "3132".  Phoenix's let() prints "312", because it only evaluates '1_p
    // << 3' once.
    {
        boost::yap::evaluate(
            let(_a = 1_p << 3)
            [
                _a << "1", _a << "2"
            ],
            std::cout
        );
    }

    std::cout << "\n";
}


PrevUpHomeNext