I use Range-v3 a lot, but mostly views and algorithms, not very much actions.
As regards ranges::actions::join, why does the following happen?
auto strs{std::vector{"one"s, "twoooooooooooooooooooooooooo"s, "three"s}};
auto str1 = join(strs) | to<std::string>; // join accepts lvalue range
auto str2 = std::move(strs) | join | to<std::string>; // join doesn't accept lvalue range
Removing std::move from the last line causes a requirement to fail in action.hpp,
#ifndef RANGES_WORKAROUND_CLANG_43400
template<typename Rng, typename ActionFn> // ******************************
friend constexpr auto // ******************************
operator|(Rng &, // ********* READ THIS **********
action_closure<ActionFn> const &) // ****** IF YOUR COMPILE *******
-> CPP_broken_friend_ret(Rng)( // ******** BREAKS HERE *********
requires range<Rng>) = delete; // ******************************
// **************************************************************************
// * When piping a range into an action, the range must be moved in. *
// **************************************************************************
#endif // RANGES_WORKAROUND_CLANG_43400
but I don't really understand why such a requirement exists for actions::join, considering that it is implemented like this:
namespace actions
{
template<typename Rng>
using join_action_value_t_ =
meta::if_c<(bool)ranges::container<range_value_t<Rng>>, //
range_value_t<Rng>, //
std::vector<range_value_t<range_value_t<Rng>>>>;
struct join_fn
{
template(typename Rng)(
requires input_range<Rng> AND input_range<range_value_t<Rng>> AND
semiregular<join_action_value_t_<Rng>>)
join_action_value_t_<Rng> operator()(Rng && rng) const
{
join_action_value_t_<Rng> ret;
auto last = ranges::end(rng);
for(auto it = begin(rng); it != last; ++it)
push_back(ret, *it);
return ret;
}
};
/// \relates actions::join_fn
/// \sa action_closure
RANGES_INLINE_VARIABLE(action_closure<join_fn>, join)
} // namespace actions
I mean, a new actual container ret is returned anyway, and rng or its elements is not even std::moved from, so what's the point of requiring that a std::moved range is piped in? And, if there's a valid reason for that, why is the constraint applied at the | level, rather than at the join level (or, in other words, why was the design such that the two lines above compile)?
And I have a somewhat symmetric doubt as regards ranges::actions::transform: this action does transform its input range in place, so I do expect it to require that the |'s lhs range is std::moved,
constexpr auto twice = [](auto s){ return s + s; };
strs = std::move(strs) | transform(twice); // std::move is necessary
but why I'm surprised I can write
strs = transform(strs, twice);