1

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.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    I'm not an expert in the library but I think you could use `front`/`drop_front` (or `drop_back`/`back`) to achieve what you want. – llonesmiz Jan 10 '16 at 07:58
  • It works thanks. There should be a better way though. It is not clear in the documentation if `drop_back` make a copy. Also I have to change to `auto const& x` but works. `boost::hana::for_each(boost::hana::drop_back(tpl), [&](auto const& x){p << x << ", "});`. It is interesting that Hana has the concept of `Iterable` but not `iterator`s. – alfC Jan 10 '16 at 08:12

4 Answers4

4

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

Louis Dionne
  • 3,104
  • 1
  • 15
  • 35
  • Does `boost::hana::intersperse` make a copy as well? Can `intersperse` be written in terms of simpler hana functions? So there is no (meta)`iterators`, `begin`, `end` in Hana? – alfC Jan 11 '16 at 03:56
  • 1
    Yes, like __all__ Hana algorithms, it makes a copy. `intersperse can be written in terms of simpler Hana functions, like you just did in you answer. However, it is bound to be less compile-time efficient and probably less runtime efficient too. – Louis Dionne Jan 11 '16 at 15:39
  • Hana does not provide iterators, see [the rationale](http://boostorg.github.io/hana/#tutorial-rationales-iterators). However, you can use indices instead of iterators to solve some problems. – Louis Dionne Jan 11 '16 at 15:40
  • That means that the `intersperse` solution is no worst than the `drop_back` solution (modulo compiler optimizations)? Is there a way to make `intersperse` take by reference (like using `std::ref` or something)? An indices based solution (yet to see) would avoid copies? – alfC Jan 11 '16 at 15:48
  • BTW, the original hack (in the question) still has the advantage that it doesn't make copies. – alfC Jan 11 '16 at 16:01
  • I added an alternative solution which doesn't make copies, would you recommend that? – alfC Jan 11 '16 at 16:11
  • 1
    __That means that the `intersperse` solution is no worst than the `drop_back` solution (modulo compiler optimizations)?__ Correct. __Is there a way to make `intersperse` take by reference (like using `std::ref` or something)?__ Yes, please see my updated answer. – Louis Dionne Jan 11 '16 at 22:26
  • __An indices based solution (yet to see) would avoid copies?__ It would be possible to compute the indices that need to be followed by a comma, and then `std::cout` the element at those indices with a comma after. Then treat the last element on its own. This would require no copy, but it would be way more cumbersome than all the solutions we have right now. – Louis Dionne Jan 11 '16 at 22:29
  • __I added an alternative solution which doesn't make copies, would you recommend that?__ This solution is more or less fine, except that you're relying on the compiler being able to compute the condition in the `if` at compile-time. Otherwise, this still works but you get a branch evaluated for each element you want to print, which is not optimal. – Louis Dionne Jan 11 '16 at 22:29
  • The code I put is just an example, I need something beyond `hana::print`. Even with the refs and all this looks better, there should a `hana::reference` or `hana::reference_transform` that does all this. – alfC Jan 11 '16 at 23:07
  • 2
    I think that `hana::transform(tuple, std::ref)` is reasonable. The only reason why you have to go through an additional lambda is because `std::ref` is specified as an overloaded function, and the language does not allow passing overloaded functions to algorithms. This is not a problem in Hana, but in the standard library or in the language. Hana can't provide a special `xxx_transform` for every possible overloaded function. – Louis Dionne Jan 12 '16 at 03:03
  • some kind of `ref_transform` is special because it solves a limitation (by design) of the library that is to make copies always. Fusion views are a pain but Hana seems to be on the other extreme. Part the problem is solved with iterators in STL but you also chose that "iterators must go". Perhaps what you need are "ranges" at the least. Anyway, Hana is a great experiment. I added a solution based on pointers below. – alfC Jan 12 '16 at 03:54
  • What do you mean, ranges? Ranges as in Eric Niebler's range-v3? These are based on iterators AFAICT. – Louis Dionne Jan 12 '16 at 14:14
  • Yes, I don't know the subtleties and the trend du-jour. I think Niebler's are based on two iterators (but not necessarely of the same type) http://ericniebler.com/2015/01/28/to-be-or-not-to-be-an-iterator/. Alexandrescu's says that "iterators must go" or be hidden completely https://www.reddit.com/r/programming/comments/976aj/iterators_must_go_presentation_by_andrei/ and Alex Stepanov says iterators are ok as they are [citation needed]. I don't understand the problem to get to a veredict myself but not having *any* is going to be painful. And at the least not very STL-like. – alfC Jan 12 '16 at 21:10
  • Going back to `ref_transform`, it looks to me that it is so difficult to create a tuple of (real) references in Hana from a tuple of values, that is could be worth having a `namespace hana{ auto ref = [](auto& t){return std::ref(t);}}` to be use as `auto r = hana::transform(t, hana::ref);` in order to have at least a tuple of `reference_wrappers`. You can also have an improved version (it is possible) of `reference_wrappers` in hana itself. – alfC Jan 12 '16 at 23:04
  • Regarding iterators, please see [the rationale](http://boostorg.github.io/hana/#tutorial-rationales-iterators) for the lack of them. Regarding references in containers, this is actually a very difficult issue because allowing them opens a huge can of worms. Right now, you can't store references in Hana containers, just like you can't do it for STL containers. As for providing a `hana::ref` function object equivalent to `std::ref`, this is clearly the job of a defect report more than Hana's job. – Louis Dionne Jan 13 '16 at 15:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100618/discussion-between-alfc-and-louis-dionne). – alfC Jan 13 '16 at 22:43
  • Can Hana tuples "store" references in the newer versions of Hana? – alfC May 26 '18 at 08:53
  • 1
    No, that is still not possible. Allowing that would require breaking changes in the `Sequence` concept, which may eventually happen but is not a trivial decision. – Louis Dionne May 29 '18 at 17:57
  • Ok, I guess you want to keep tuples strictly Value types. I understand. Perhaps a tuple with references shouldn't be Sequences and that's it. On the flip side, of course a tuple of all-refences would be nice if it behave as a reference object in itself (perhaps not possible 100% with the current language), I wouldn't know how to call this concept (`Codex`?). And problably all intermediate possibilities will not fulfill any meaningful concept. – alfC May 29 '18 at 18:51
0

Thanks to @cv_and_he, I was able to get a solution. Although it doesn't look like the most elegant because it would result in code duplication (and also in a copy).

template<class P, class... Ts>
decltype(auto) operator<<(
    std::ostream& os, 
    boost::hana::tuple<Ts...> const& tpl
){  
    os << "{";
    boost::hana::for_each(
        boost::hana::drop_back(tpl), [&](auto const& x){
            os << x << ", ";
        }
    );
    os << boost::hana::back(x);
    return os << "}";
}
alfC
  • 14,261
  • 4
  • 67
  • 118
0

Same as the original but less hack since it uses boost::hana::equal to compare identities.

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(not boost::hana::equal(&x, &boost::hana::back(tpl))){p << ", ";}
        }
    );
    return os << "}";
}
alfC
  • 14,261
  • 4
  • 67
  • 118
  • Actually, I like the `==` version better. Indeed, since you are comparing addresses, `==` and `hana::equal` are equivalent. However, using `hana::equal` makes it less obvious what the purpose is, because `hana::equal` is frequently used to do _compile-time_ comparisons (e.g. `hana::equal(hana::int_c<1>, hana::long_c<1>)`, which returns `hana::true_c`). Just a personal preference, really. – Louis Dionne Jan 11 '16 at 22:13
  • The `hana::equal` version, if I understand correctly it will give a compile time `false` if the types (of the pointers) are different. It will be a runtime `true/false` if the types are the same. I have the impression in the back of my mind that an optimal solution should be always a compile time constant. – alfC Jan 11 '16 at 23:04
  • It is impossible to always give a compile-time constant, since the actual address of `&hana::back(tpl)` is a runtime value. For example, what happens if `tpl` is allocated on the heap? How could its address be known at compile-time? – Louis Dionne Jan 12 '16 at 03:00
  • @LouisDionne, not always but when types are different maybe the argument type is ignored false is returned always, correct me if I am wrong `template constexpr bool equal(T1 const&, T2 const&){return false;}` and `template bool equal(T const& t1, T const& t2){...runtime code...;}` ?? – alfC Jan 12 '16 at 03:18
  • More or less, yes. In fact, if the two pointers are of different types __that cannot be compared__, then a compile-time `false` value will be returned. – Louis Dionne Jan 12 '16 at 14:10
0

This is a solution based on pointers to avoid both copies and std::cref.

template<class P, class... Ts>
decltype(auto) operator<<(
    std::ostream& os, 
    boost::hana::tuple<Ts...> const& tpl
){  
    os << "{";
    std::string sep = ", ";
    hana::for_each(
        hana::intersperse(
            hana::transform(tpl, [](auto& t){return &t;}),
            &sep
        ), [&](auto x){os << *x;}
    );
    return os << "}";
}
alfC
  • 14,261
  • 4
  • 67
  • 118