1432. random_shuffle signatures are inconsistent

Section: 26.7.13 [alg.random.shuffle] Status: C++11 Submitter: INCITS Opened: 2010-08-25 Last modified: 2016-01-28

Priority: Not Prioritized

View all other issues in [alg.random.shuffle].

View all issues with C++11 status.

Duplicate of: 1433

Discussion:

Addresses US-121, GB-119

random_shuffle and shuffle should be consistent in how they accept their source of randomness: either both by rvalue reference or both by lvalue reference.

[ Post-Rapperswil, Daniel provided wording ]

The signatures of the shuffle and random_shuffle algorithms are different in regard to the support of rvalues and lvalues of the provided generator:

template<class RandomAccessIterator, class RandomNumberGenerator>
void random_shuffle(RandomAccessIterator first,
RandomAccessIterator last,
RandomNumberGenerator&& rand);
template<class RandomAccessIterator, class UniformRandomNumberGenerator>
void shuffle(RandomAccessIterator first,
RandomAccessIterator last,
UniformRandomNumberGenerator& g);

The first form uses the perfect forwarding signature and that change compared to C++03 was done intentionally as shown in the first rvalue proposal papers.

While it is true, that random generators are excellent examples of stateful functors, there still exist good reasons to support rvalues as arguments:

  1. If one of the shuffle algorithms is called with the intention to shuffle items with a reproducible ordering from a given generator class, it makes sense to create a generator exactly at the call point.
  2. Other algorithms with similar need for stateful functors (like std::generate and std::generate_n) accept both rvalues and lvalues as well.
  3. Given the deduction rules for perfect forwarding it is hard for a user to produce code that does the wrong thing unintentionally. Any lvalue generator will deduce an lvalue-reference and behave as in C++03. In the specific cases, where rvalues are provided, the argument will be accepted instead of being rejected.

Arguments have been raised that accepting rvalues is error-prone or even fundamentally wrong. The author of this proposal disagrees with that position for two additional reasons:

  1. Enforcing lvalues as arguments won't prevent user code to enforce what they want. So given
    my_generator get_generator(int size);
    
    instead of writing
    std::vector<int> v = ...;
    std::shuffle(v.begin(), v.end(), get_generator(v.size()));
    
    they will just write
    std::vector<int> v = ...;
    auto gen = get_generator(v.size());
    std::shuffle(v.begin(), v.end(), gen);
    
    and feel annoyed about the need for it.
  2. Generators may be copyable and movable, and random number engines are required to be CopyConstructible and this is obviously a generally useful property for such objects. It is also useful and sometimes necessary to start a generator with exactly a specific seed again and again and thus to provide a new generator (or a copy) for each call. The CopyConstructible requirements allow providing rvalues of generators and thus this idiom must be useful as well. Therefore preventing [random_]shuffle to accept rvalues is an unnecessary restriction which doesn't prevent any user-error, if there would exist one.

Thus this proposal recommends to make both shuffle functions consistent and perfectly forward-able.

Moved to Tentatively Ready after 6 positive votes on c++std-lib.

[ Adopted at 2010-11 Batavia ]

Proposed resolution:

  1. Change [algorithms.general], header <algorithm> synopsis as indicated:
    template<class RandomAccessIterator, class UniformRandomNumberGenerator>
    void shuffle(RandomAccessIterator first, RandomAccessIterator last,
    UniformRandomNumberGenerator&& rand);
    
  2. Change the prototype description of [alg.random.shuffle] as indicated:
    template<class RandomAccessIterator, class UniformRandomNumberGenerator>
    void shuffle(RandomAccessIterator first, RandomAccessIterator last,
    UniformRandomNumberGenerator&& rand);