Why Are Std::Shuffle Methods Being Deprecated in C++14

Why are std::shuffle methods being deprecated in C++14?

std::random_shuffle may make use, under the hood, of random C family of functions. These functions use global state for seeds and other state.

So it is being deprecated because shuffle will do the same, but better. Namely, it uses the new <random> header from C++11 that doesn't use global state, but its own objects making use of generators, devices and distributions.

Why will std::rel_ops::operators be deprecated in C++20?

In C++20, you get three-way comparison (operator <=>), which automatically "generates" default comparisons if provided:

struct A {
// You only need to implement a single operator.
std::strong_ordering operator<=>(const A&) const;
};

// Compiler generates 4 relational operators (you need to default the
// three-way comparison operator to get == and !=).
A to1, to2;
if (to1 > to2) { /* ... */ } // ok
if (to1 <= to2) { /* ... */ } // ok, single call to <=>

There are multiple advantages of the three-way comparison over std::rel_ops, which is probably why std::rel_ops operators are deprecated. On top of my head:

  • It is more versatile, since, depending on the return type of operator<=> (std::strong_ordering, std::weak_ordering, ...), only relevant operators are generated. See the <compare> header for more information.

  • You do not bring a bunch of templated operator overloads by doing using namespace std::rel_ops.

  • You can ask the compiler to generate the three-way operator for you by defaulting it (auto operator<=>(A const&) = default) — This will basically generate a lexicographic comparison of base classes and non-static data members, plus it will deduce the right type of ordering if the return type is auto.

How to shuffle an array in C++?

Here's your code with what I felt were the smallest amount of changes. One could argue that I didn't need to change your first for loop as much, but I figure that if you have the prescience to know how many names you're reading, you might as well use the knowledge.

#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator> // std::begin(), std::end(); required for C-arrays
#include <random> // std::mt19937; needed to feed std::shuffle()
#include <string>
// using namespace std; // BAD PRACTICE

int main() {
constexpr int size = 4; // Give your magic number a name; only need to change
// a single location
std::ifstream readName("names.txt");
if (!readName) { // Always check that you successfully opened the file.
std::cerr << "Error opening file.\n";
return 1;
}

std::string names[size];
// int i = 0;
for (int i = 0; i < size; ++i) { // Retool the loop entirely
std::getline(readName, names[i]);
}
readName.close();

// This is a fragile solution. It's only working because the array is in
// scope
std::shuffle(std::begin(names), std::end(names),
std::mt19937{std::random_device{}()});

for (int i = 0; i < size; i++) {
std::cout << names[i]
<< '\n'; // Don't use std::endl unless you actually need it
}
return 0;
}

This is not ideal code, though. Any change to the size of the input file requires changing the code and re-compiling. The biggest single change was to get rid of std::random_shuffle and use std::shuffle() instead. std::random_shuffle was deprecated with C++14 and removed in C++17. It's bad to use it. std::shuffle() does add the requirement of providing a PRNG, but it's not so bad. It leads to better code if you have a PRNG that needs to randomize many different things in a bigger program. This is because it's good to have a single PRNG and let it live for the length of your program as opposed to constantly building new ones.

And the C-array just makes things a bit clunkier. Enter std::vector.

#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <random> // std::mt19937; needed to feed std::shuffle()
#include <string>
#include <vector>

int main() {
std::ifstream readName("names.txt");
if (!readName) { // Always check that you successfully opened the file.
std::cerr << "Error opening file.\n";
return 1;
}

std::vector<std::string> names;
std::string name;
while (std::getline(readName, name)) { // Retool the loop entirely
names.push_back(name);
}
readName.close();

std::shuffle(std::begin(names), std::end(names),
std::mt19937{std::random_device{}()});

for (const auto& i : names) {
std::cout << i << '\n';
}

return 0;
}

The vector can grow as needed, so you see how much simpler the loop that reads the names becomes. It's also more flexible since you don't have to know ahead of time how many entries to expect. It will "just work." With the call to std::shuffle() I kept the std::begin(names) syntax because many consider this a best practice, but you could have also used names.begin() if you wanted since the vector class provides its own iterators.

What are best practices for simple random shuffling in code that's both C++03 and C++14?

Even in C++11, distributions are not standardized in implementation.

Write your own shuffler (for each element, swap it with another random elememt) and random number generator/distribution. Weak, slow random number generators are short and simple.

I would pass your 'random factory' on down, and habe a method to 'fork' on thread spawning, as doing so also lets you do multiple 'runs' in the same execution. Having explicit state instead of global is usually worthwhile. But not needed if single threaded: just stuff the random factory into some global state and hold your nose.

There is no random shuffle that will bridge C++03 to C++17, so embrace a short hand written one. It also ensures the same behaviour on multiple platforms, which is useful for a number of reasons (test coverage (same on different platforms), cross platform testing (bug on OS/X can be debugged on Windows), portability of myriads of stuff (save game files, io-based network gameplay, etc)).

Error when trying to shuffle std::vector

That's because you're calling random_shuffle incorrectly:

std::random_shuffle(stripes.begin(), stripes.end(), seed);

It doesn't take a seed, it takes a function:

template< class RandomIt, class RandomFunc >
void random_shuffle( RandomIt first, RandomIt last, RandomFunc& r );

Where the RandomFunc is a function that, when called with n should return a random number in the range [0,n). It's an implementation of the Knuth Shuffle. You probably meant to call:

srand(seed);
std::random_shuffle(stripes.begin(), stripes.end()); // typically uses rand()

Or, with C++11:

std::shuffle(stripes.begin(), stripes.end(), std::mt19937{seed});

How do I pass standard generators to STL functions?

The overload of std::random_shuffle you're trying to use takes a "RandomFunc":

function object returning a randomly chosen value of type convertible to std::iterator_traits<RandomIt>::difference_type in the interval [0,n) if invoked as r(n)

In C++14, std::random_shuffle is deprecated in favour of std::shuffle, which takes a generator rather than that "RandomFunc". Simply change the function to std::shuffle.

Is it okay that random_shuffle uses rand()?

The preferred method is to use std::shuffle:

std::shuffle(begin(), end(), std::mt19937());

std::random_shuffle()'s source of randomness is undefined. The Visual C++ team has commented on this, saying they're going to keep it using rand() rather than something better so that it will remain backward-compatible.

Random_shuffle alternative in C++Builder6

The standard algorithm to shuffle an array is the Fisher-Yates algorithm. That is easy to implement and there are a number of examples on the web. However, if you are learning then it would be a good idea to program it from scratch rather than blindly copy someone else's code.



Related Topics



Leave a reply



Submit