The logs in the test output should hopefully make a case that recommending this syntax in the guidelines can be very problematic: std::bind(distrib, engine)
.
Depending on the testing code, this can actually turn into just glorified fixed-case testing.
// Look through the test output logs and expand to reveal commentary. No need to check the test code.
// Look through the test output logs and expand to reveal commentary. No need to check the test code.
#include <random>
#include <functional>
#include <fmt/ranges.h>
Describe(pitfalls_of_using_bind_without_ref)
{
It(context)
{
std::cout << R"(std::bind(distrib, engine) makes a copy of the engine at the time of its invocation.
Therefore, if you create different random-generation functions that
use std::bind, each time you call them, it’s as if you’re
generating the first number from the same initial
engine state. This can result in a very non-random output at times.)";
}
It(using_multiple_functions)
{
std::mt19937 engine{ std::random_device{}() };
std::function<int()> gen_a = std::bind(std::uniform_int_distribution{-1000, 1000}, engine);
std::function<int()> gen_b = std::bind(std::uniform_int_distribution{-1000, 1000}, engine);
std::cout << "std::function<int()> gen_a\n = std::bind(std::uniform_int_distribution{-1000, 1000}, engine);\n";
std::cout << "std::function<int()> gen_b\n = std::bind(std::uniform_int_distribution{-1000, 1000}, engine);\n\n";
std::cout << "gen_a and gen_b seem like they would return different results (or so we think!).\nLet's call them in a loop\n\n";
for (int i = 0; i < 10; ++i) {
std::cout << "Next Iteration:\n"
<< "gen_a(): " << gen_a() << '\n'
<< "gen_b(): " << gen_b() << "\n\n";
}
}
It(using_with_stl_algorithms)
{
std::mt19937 engine{ std::random_device{}() };
std::function<int()> gen_int = std::bind(std::uniform_int_distribution{-1000, 1000}, engine);
std::cout << "std::function<int()> gen_int\n = std::bind(std::uniform_int_distribution{-1000, 1000}, engine);\n\n"
<< "Let's make a loop where we create and fill up a\nvector with random ints each time.\n\n"
<< "std::vector<int> vec(10);\n"
<< "std::generate(vec.begin(), vec.end(), gen_int);\n\n";
for (int i = 0; i < 3; ++i) {
std::vector<int> vec(10);
std::generate(vec.begin(), vec.end(), gen_int);
std::cout << fmt::format("Iteration #{} using std::generate:\n{}\n\n", i, vec);
}
std::cout << "Same sequence every time :/.\n\nAfter all that, manually calling gen_int() a bunch of times\nalso returns the same sequence:\n";
for (int _ = 0; _ < 10; ++_) std::cout << gen_int() << " ";
}
};
Describe(solution1__bind_with_std_ref)
{
It(context)
{
std::cout << R"(An easy solution is still using std::bind
but providing a reference-wrapper to the engine
via std::ref (from the <functional> header).
std::bind(std::uniform_int_distribution{-1000, 1000}, engine);
becomes...
std::bind(std::uniform_int_distribution{-1000, 1000}, std::ref(engine));
)";
}
It(using_multiple_functions)
{
std::mt19937 engine{ std::random_device{}() };
std::function<int()> gen_a = std::bind(std::uniform_int_distribution{-1000, 1000}, std::ref(engine));
std::function<int()> gen_b = std::bind(std::uniform_int_distribution{-1000, 1000}, std::ref(engine));
std::cout << "std::function<int()> gen_a\n = std::bind(std::uniform_int_distribution{-1000, 1000}, std::ref(engine));\n";
std::cout << "std::function<int()> gen_b\n = std::bind(std::uniform_int_distribution{-1000, 1000}, std::ref(engine));\n\n";
std::cout << "gen_a and gen_b are no more identical.\nLet's call them in a loop\n\n";
for (int i = 0; i < 10; ++i) {
std::cout << "Next Iteration:\n"
<< "gen_a(): " << gen_a() << '\n'
<< "gen_b(): " << gen_b() << "\n\n";
}
}
It(using_with_stl_algorithms)
{
std::mt19937 engine{ std::random_device{}() };
std::function<int()> gen_int = std::bind(std::uniform_int_distribution{-1000, 1000}, std::ref(engine));
std::cout << "std::function<int()> gen_int\n = std::bind(std::uniform_int_distribution{-1000, 1000}, std::ref(engine));\n\n"
<< "Let's make a loop where we create and fill up a\nvector with random ints each time.\n\n"
<< "std::vector<int> vec(10);\n"
<< "std::generate(vec.begin(), vec.end(), gen_int);\n\n";
for (int i = 0; i < 3; ++i) {
std::vector<int> vec(10);
std::generate(vec.begin(), vec.end(), gen_int);
std::cout << fmt::format("Iteration #{} using std::generate:\n{}\n\n", i, vec);
}
std::cout << "After all that, manually calling gen_int() a bunch of times:\n";
for (int _ = 0; _ < 10; ++_) std::cout << gen_int() << " ";
std::cout << "\n\nAs evident, we don't have the same identical-number issue.";
}
};
Describe(solution2__using_lambdas_with_reference_capture)
{
It(context)
{
std::cout << R"(Using a lambda that captures the engine
by reference can also negate these issues.)
std::bind(std::uniform_int_distribution{-1000, 1000}, engine);
becomes...
[&, dis=std::uniform_int_distribution{-1000, 1000}]() mutable { return dis(engine); };
It's ugly but elides the issue.)";
}
It(using_multiple_functions)
{
std::mt19937 engine{ std::random_device{}() };
std::function<int()> gen_a = [&, dis=std::uniform_int_distribution{-1000, 1000}]() mutable { return dis(engine); };
std::function<int()> gen_b = [&, dis=std::uniform_int_distribution{-1000, 1000}]() mutable { return dis(engine); };
std::cout << "std::function<int()> gen_a\n = [&, dis=std::uniform_int_distribution{-1000, 1000}]() mutable { return dis(engine); };\n";
std::cout << "std::function<int()> gen_b\n = [&, dis=std::uniform_int_distribution{-1000, 1000}]() mutable { return dis(engine); };\n\n";
std::cout << "gen_a and gen_b are no more identical.\nLet's call them in a loop\n\n";
for (int i = 0; i < 10; ++i) {
std::cout << "Next Iteration:\n"
<< "gen_a(): " << gen_a() << '\n'
<< "gen_b(): " << gen_b() << "\n\n";
}
}
It(using_with_stl_algorithms)
{
std::mt19937 engine{ std::random_device{}() };
std::function<int()> gen_int = [&, dis=std::uniform_int_distribution{-1000, 1000}]() mutable { return dis(engine); };
std::cout << "std::function<int()> gen_int\n = [&, dis=std::uniform_int_distribution{-1000, 1000}]() mutable { return dis(engine); };\n\n"
<< "Let's make a loop where we create and fill up a\nvector with random ints each time!\n\n"
<< "std::vector<int> vec(10);\n"
<< "std::generate(vec.begin(), vec.end(), gen_int);\n\n";
for (int i = 0; i < 3; ++i) {
std::vector<int> vec(10);
std::generate(vec.begin(), vec.end(), gen_int);
std::cout << fmt::format("Iteration #{} using std::generate:\n{}\n\n", i, vec);
}
std::cout << "After all that, manually calling gen_int() a bunch of times:\n";
for (int _ = 0; _ < 10; ++_) std::cout << gen_int() << " ";
std::cout << "\n\nAs evident, we don't have the same identical-number issue.";
}
};