7

Using the range-v3 library (by @EricNiebler), makes writing algorithmic code much more compact, e.g. here's how to generate a bunch of random numbers:

#include <range/v3/all.hpp>
#include <iostream>
#include <vector>

int main() 
{
    using namespace ranges;

    auto const N = 10;
    std::vector<int> v; 
    v.reserve(N);

    v |= action::push_back(view::iota(0, N)); 
    random_shuffle(v);
    copy(v, ostream_iterator<>(std::cout, ","));
}

Live Example.

However, I would prefer to extend the pipeline with a hypothetical action::random_shuffle() like this

v |= action::push_back(view::iota(0, N)) | action::random_shuffle();

Here's my attempt at writing such an action (unfortunately, writing new range-v3 code is quite a bit more verbose than using the library)

#include <functional> // bind, placeholders::_1

namespace ranges
{
    inline namespace v3
    {
        /// \addtogroup group-actions
        /// @{
        namespace action
        {
            struct random_shuffle_fn
            {
            private:
                friend action_access;

                static auto bind(random_shuffle_fn random_shuffle)
                RANGES_DECLTYPE_AUTO_RETURN
                (
                    std::bind(random_shuffle, std::placeholders::_1)
                )

                template<typename Gen>
                static auto bind(random_shuffle_fn random_shuffle, Gen && rand)
                RANGES_DECLTYPE_AUTO_RETURN
                (
                    std::bind(random_shuffle, std::placeholders::_1, bind_forward<Gen>(rand))
                )
            public:
                struct ConceptImpl
                {
                    template<typename Rng,
                        typename I = range_iterator_t<Rng>>
                    auto requires_(Rng&&) -> decltype(
                        concepts::valid_expr(
                            concepts::model_of<concepts::RandomAccessRange, Rng>(),
                            concepts::is_true(Permutable<I>())
                        ));
                };

                template<typename Rng>
                using Concept = concepts::models<ConceptImpl, Rng>;

                template<typename Rng,
                    CONCEPT_REQUIRES_(Concept<Rng>())>
                Rng operator()(Rng && rng) const
                {
                    ranges::random_shuffle(rng);
                    return std::forward<Rng>(rng);
                }

                template<typename Rng, typename Gen,
                    CONCEPT_REQUIRES_(Concept<Rng>())>
                Rng operator()(Rng && rng, Gen && rand) const
                {
                    ranges::random_shuffle(rng, std::forward<Gen>(rand));
                    return std::forward<Rng>(rng);
                }

                #ifndef RANGES_DOXYGEN_INVOKED
                template<typename Rng>
                void operator()(Rng &&) const
                {
                    CONCEPT_ASSERT_MSG(RandomAccessRange<Rng>(),
                        "The object on which action::random_shuffle operates must be a model of the "
                        "RandomAccessRange concept.");
                    using I = range_iterator_t<Rng>;
                    CONCEPT_ASSERT_MSG(Permutable<I>(),
                        "The iterator type of the range passed to action::random_shuffle must allow its "
                        "elements to be permuted; that is, the values must be movable and the "
                        "iterator must be mutable.");
                }
            #endif
            };

            /// \ingroup group-actions
            /// \relates sort_fn
            /// \sa `action`
            namespace
            {
                constexpr auto&& random_shuffle = static_const<action<random_shuffle_fn>>::value;
            }
        }
        /// @}
    }
}

Live Example which fails to compile because some operator() deeply hidden somewhere is not being found.

As far as I can see, I faithfully transled the above code from similar code for e.g. the action::sort(). The only difference is that the random_shuffle() has two overloads (one taking a random generator), whereas all the other actions (including sort) all have a single overload with default values for their extra parameters (comparators, predicates, projectors etc.). This translates into two bind() static member functions of random_shuffle_fn above, whereas all other actions only have a single bind() overload.

Question: how to write a range-v3 action for random_shuffle?

Xeo
  • 129,499
  • 52
  • 291
  • 397
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Not totally relevant to the question, but: `std::random_shuffle` is deprecated. Use `std::shuffle`. – Xeo May 24 '15 at 15:55
  • Style-wise, I think I'd prefer `std::vector v = view::iota(0, N); v |= action::random_shuffle;` or even `auto v = std::vector{view::iota(0, N)} | action::random_shuffle;` to eliminate the `reserve` call. – Casey May 24 '15 at 17:28
  • @Casey will the `iota` call `reserve`? – TemplateRex May 24 '15 at 17:31
  • Well, I would if `std::vector{view::iota(0, N)}` was valid. Sorry, I've been implementing range-v3-aware containers so I've become accustomed to that syntax ;) – Casey May 24 '15 at 17:31
  • If `view::iota(0, N)` had random access iterators, any reasonable `vector` implementation would reserve the right amount of space in its constructor. Views have to have input iterators in C++14 though - sorry again, I've been working with Concepts and N4382 Ranges too much lately. Consider that entire comment retracted. – Casey May 24 '15 at 17:34
  • @Casey somehow, `action::push_back` should infer that it is pushing back into a `std::vector` from a sized-range and do the `reserve` itself. – TemplateRex May 24 '15 at 17:41
  • Tracing through code...`push_back(container, range)` delegates to `insert(container, end(container), range)` which calls `container.insert(pos, begin(range), end(range))`. `std::vector::insert` is probably smart enough to reserve space when passed random access iterators, so this will "just work" in STL2 since random access views are possible. But yes, a vector-specific overload of `ranges::push_back` for SizedIteratorRanges would be nice. – Casey May 24 '15 at 17:55
  • @Casey even if vector::insert is smart for random access ranges today, it won't be smart enough when a sized-forward range such as `std::forward_list` is being inserted, even though the list size is known – TemplateRex May 24 '15 at 18:03
  • 1
    Done: [Reserve capacity before inserting a SizedRange into a std::vector.](https://github.com/CaseyCarter/range-v3/commit/12064adf4f67e511043ffd6d986ab076bacd9b35) – Casey May 24 '15 at 18:43

2 Answers2

4

The latest version from git already contains action::shuffle. It can be used as follows:

#include <random>
std::mt19937 gen;
...
v |= action::push_back(view::iota(0, N)) | action::shuffle(gen);
Floop
  • 451
  • 4
  • 10
3

You have two ambiguous overloads of random_shuffle_function::operator()(Rng&&), your "error catching" overload needs to be constrained to accept only those arguments that the proper overload rejects (we really need C++ Concepts so I never again have to SFINAE constrain overloads):

#ifndef RANGES_DOXYGEN_INVOKED
template<typename Rng,
    CONCEPT_REQUIRES_(!Concept<Rng>())>
void operator()(Rng &&) const
{
    CONCEPT_ASSERT_MSG(RandomAccessRange<Rng>(),
        "The object on which action::random_shuffle operates must be a model of the "
        "RandomAccessRange concept.");
    using I = range_iterator_t<Rng>;
    CONCEPT_ASSERT_MSG(Permutable<I>(),
        "The iterator type of the range passed to action::random_shuffle must allow its "
        "elements to be permuted; that is, the values must be movable and the "
        "iterator must be mutable.");
}
#endif

Also, you need to pipe through action::random_shuffle:

v |= action::push_back(view::iota(0, N)) | action::random_shuffle;

DEMO

Casey
  • 41,449
  • 7
  • 95
  • 125
  • great, thanks for getting it to work. The missing CONCEPTS_REQUIRE was a pure oversight, from cutting some extra template parameters that I copied from the `action::sort` code. I don't know why I put the parenthesis on `action::random_shuffle`, perhaps because I expected it to be a nullary function object. So what is actually being called here? – TemplateRex May 24 '15 at 17:36
  • 1
    another comment: please consider making this a pull request to Github! The world needs more action :) – TemplateRex May 24 '15 at 17:37
  • 2
    I would gladly accept a pull request for `action::shuffle`. Not for `action::random_shuffle`. – Eric Niebler May 25 '15 at 03:41