I was using a std::uniform_int_distribution
to generate primes (p)
. I put the distribution object in an anonymous namespace - which seems like C++ 'static linkage' for grown-ups...
namespace
{
// a more pedantic range: [2, 18446744073709551557]
std::uniform_int_distribution<uint64_t> p_dist {2};
std::mt19937 rng; // (sufficient IV states for uniqueness)
}
Note, that I seed the Mersenne Twister as thoroughly as portable code will allow. This isn't really important to the question though. It's just to assure the reader I'm using the random facilities properly:
std::seed_seq::result_type data[rng.state_size];
std::random_device rdev;
std::generate_n(data, rng.state_size, std::ref(rdev));
std::seed_seq rng_seed (data, data + rng.state_size);
rng.seed(rng_seed);
This is very convenient, as I have a deterministic u64_prime(p)
function, using (7) bases
, that can determine if (p)
is prime:
uint64_t p;
while (!u64_prime(p = p_dist(rng)))
;
Now I create a std::function
object:
std::function<uint64_t()> zp_rng = std::bind(
decltype(p_dist){0, p - 1}, std::ref(rng));
This function: zp_rng()
can be invoked to return a random number in Z(p)
. That is, using the distribution object for: [0, p - 1]
from the results of the referenced rng.
Now this is very impressive - but I've effectively adopted it by cut-and-paste with little understanding of the interaction between std::function
and the interaction of the parameters given to std::bind
.
I'm not confused by decltype(p_dist){0, p - 1}
- that's just a way to specify we still want to use a std::uniform_int_distribution
. My understanding of std::ref(rng)
is that it prevents a local copy of the rng being instantiated, and forces the use of reference instead... so:
Q: What are the basic rules that effectively determine: dist(rng)
being used - I don't see why std::bind
would enforce this interaction. A lot of interactions seem based around operator ()
methods.
Q: std::function
is helpfully referred to as 'a general-purpose polymorphic function wrapper' on cppreference.com. So is it a function that encapsulates a uint64_t
return type? Or again, making use of operator ()
syntax to drive the notion of a function?
As incredibly useful as these constructs are, I feel like I'm cargo-cult programming to a degree here. I'm looking for an answer which resolves any ambiguities in a concrete way, and adds insight to similar questions - how are bind
arguments expected to interact, and how does the function
signature reflect that?
I'm not getting any positive feedback about the use of std::bind
. Plenty on the superior results (and code generation) of simply using lambda functions, even in such a simple case. My own tests validate this.