Search code examples
c++boostboost-fusionboost-hana

How to write a for loop for a Hana sequence?


I have a Boos.Hana sequence and I would like to print it to screen separated by commas. However the commas separate elements only, so I have to check if I am at the last element.

Currently my hack is pretty bad (looking at the pointer and casting to void*.

template<class P, class... Ts>
decltype(auto) operator<<(
    std::ostream& os, 
    boost::hana::tuple<Ts...> const& tpl
){  
    os << "{";
    boost::hana::for_each(
        tpl, [&](auto& x){
            os << x;
            if((void*)&boost::hana::back(tpl) != (void*)&x) os << ", ";
        }
    );
    return os << "}";
}

In the case of Boost.Fusion it was more complicated because I use fusion iterators (boost::fusion::begin and boost::fusion::end) but at least I could compare the iterators. (bool last = result_of::equal_to<typename result_of::next<First>::type, Last>::value).

Another way to ask this question is if there are (meta) iterators in Hana.


Solution

  • First, to answer your comment, drop_back does make a copy. All algorithms in Hana make copies and are eager, as documented here.

    Secondly, you could use hana::intersperse to add a comma between each element, resulting in something like

    template<class P, class... Ts>
    decltype(auto) operator<<(
        std::ostream& os, 
        boost::hana::tuple<Ts...> const& tpl
    ){  
        os << "{";
        boost::hana::for_each(boost::hana::intersperse(tpl, ", "), 
            [&](auto const& x){
                os << x;
            });
        return os << "}";
    }
    

    However, the best solution would probably be to use experimental::print, which does exactly what you want:

    #include <boost/hana/experimental/printable.hpp>
    #include <boost/hana/tuple.hpp>
    #include <iostream>
    
    int main() {
        auto ts = hana::make_tuple(1, 2, 3);
        std::cout << hana::experimental::print(ts);
    }
    

    Edit

    If you want to use the intersperse solution, but do not want to make a copy of the sequence, you can do the following:

    #include <boost/hana.hpp>
    #include <functional>
    #include <iostream>
    namespace hana = boost::hana;
    
    template <class... Ts>
    decltype(auto) operator<<(std::ostream& os, hana::tuple<Ts...> const& tpl) {
        os << "{";
        char const* sep = ", ";
        auto refs = hana::transform(tpl, [](auto const& t) { return std::ref(t); });
        hana::for_each(hana::intersperse(refs, std::ref(sep)),
            [&](auto const& x){
                os << x.get();
            });
        return os << "}";
    }
    

    But really, you should probably be using hana::experimental::print. And if your use case is performance critical and you want to avoid creating a std::string, I would question the usage of std::ostream in the first place.

    End of edit