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.
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
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 << "}";
}
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 << "}";
}
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 << "}";
}