Binding Function Arguments with std::bind
In the die example, the first argument is the distribution named one_to_six and the second argument is the engine named re.
Predicting What die() Does
A common first reaction to the C++ standard library's random number facility is that it must be the easy, ready-made choice for anyone who needs dice rolls or random integers. The library is powerful and carefully designed, so the assumption feels reasonable. The source for this lesson argues against it. Experts praise the library's generality and performance, yet the same discussion states plainly that the library can hardly be considered novice friendly. Keep that in mind as you go. The goal here is not to memorize every piece of the interface, but to see how a small number of parts fit together so that the one generator you need becomes readable to you.
What do you think happens?
A program creates a generator named die and then calls die(). What is die() doing when it is called: something new you have to learn from scratch, or something you already know how to interpret?
Reveal answer
die() is a callable built by combining two known parts, a distribution and an engine. Calling die() is equivalent to calling the distribution with the engine as its argument.
The name die is only a handle. Behind it sits a distribution applied to an engine. The rest of this article takes those two parts apart and then shows how bind() joins them.
Two Parts: Engine and Distribution
A random number generator in the C++ standard library is created by combining two things: an engine, which is the source of randomness, and a distribution, which maps engine output to a specific range. Each part has a separate job, and keeping those jobs separate is the key to reading the code.
The engine is the part that produces the raw values. It does not know anything about dice or faces; it simply supplies numbers from its own large output range. The distribution is the part that takes those raw values and shapes them into the range your program needs. For a die, that range is 1 to 6. Because the two jobs are separate, you can change one without rewriting the other: a different engine or a different distribution can be swapped in when a problem requires it.
The source frames this separation as the central idea of the lesson. Neither part is a complete random number generator on its own. The engine alone gives you raw randomness that is not yet a die roll, and the distribution alone has nothing to draw from. The generator exists only when the two are combined.
Engine Versus Distribution Side by Side
| Question | Engine | Distribution |
|---|---|---|
| What is its job? | Source of randomness | Maps engine output to a specific range |
| What does it produce? | Raw values from its own output range | Values within the range it was configured with, such as 1 to 6 |
| Which name in the die example? | re | one_to_six |
| Does it work alone? | Not as a die; its raw values are not yet a die roll | Not without an engine to draw values from |
| What to check when results are wrong? | The engine declaration | The distribution arguments |
The two parts of a generator and the roles they play
How bind() Wires the Pieces Together
The standard-library function bind() makes a function object. That object invokes its first argument with its second argument. In the die example, the first argument is the distribution named one_to_six and the second argument is the engine named re. The result of bind(one_to_six, re) is a new callable that the program stores under the name die. The source states the consequence directly: making die equivalent to one_to_six(re). Calling die() therefore amounts to calling one_to_six(re).
one_to_sixis the distribution, placed first- re is the engine, placed second
- std::bind returns a new callable that is stored as die
die()takes no arguments; the engine is already attached
The order of the arguments matters. The distribution goes in first and the engine goes in second. The source's wiring description makes this explicit: the distribution goes in first, the engine goes in second, and bind produces the callable the program stores as die. Once that callable exists, the engine is attached to it, so the caller never has to pass the engine again.
Tracing a Single Call to die()
Now follow one call from start to finish. Each step below names the function that runs and the part that is asked for a value. Notice that only one step actually asks the engine for a number, and that the other steps are forwarding the request along the chain that bind() built.
One roll of the die
The program has built die with bind(one_to_six, re) and now calls die(). Trace which functions run, in what order, and which step asks the engine for a number.
- 1Call die()
The program calls
die(). Because die was made by bind, this call is forwarded to its stored target. - 2Forward to one_to_six
The bound object calls
one_to_sixwith the engine re as its argument, which is the same as callingone_to_six(re). - 3Request from the engine
The distribution needs a value to shape, so it asks re for a raw value. This is the only step that touches the engine.
- 4Shape into 1 to 6
The distribution takes the raw value and maps it into the range it was configured with, 1 to 6.
- 5Return the roll
The shaped value travels back through
die()to the caller, which receives a roll from 1 to 6.
A single call to die() produces one roll from 1 to 6, and the engine is consulted exactly once along the way.
Finding Where a Bad Value Comes From
Because the generator is built from separate parts, you can locate a fault by asking which part supplies each value. The source gives a fixed order for this check: the engine, then the distribution arguments, then the bind() call. Work through them in that order rather than guessing.
Common mistake Looking for the fault in the wrong part of the pipeline
Why it failsIf the values are not coming from the engine, checking the distribution arguments wastes time. Each symptom points to a specific part.
Do this insteadAsk which part supplies the value. If the values are not coming from the engine, check the engine declaration first. If the range is wrong, check the distribution arguments. If the call does not behave as expected, check the bind() call and the order of its arguments.
Common mistake Swapping the distribution and engine in the bind() call
Why it failsbind()calls its first argument with its second. Reversing them asks the wrong thing to do the wrong job.Do this insteadPlace the distribution first and the engine second, so that die behaves as one_to_six(re).
Common mistake Assuming the engine alone produces die rolls
Why it failsThe engine supplies raw values, not values in the 1 to 6 range. Its output is not yet a die roll.
Do this insteadRemember that the distribution is the part that maps engine output to the required range.
Common mistake Choosing the most general interface before you need it
Why it failsThe source notes that for novices the fully general interface can be a serious obstacle. A simple generator is often enough to start.
Do this insteadBuild the simplest generator that meets your need first, and add generality only when a problem requires a different engine or distribution.
Naming the Parts Versus Compact Construction
The source shows two ways to build the same generator. The first declares the distribution and engine as separate named objects, then combines them with bind(). The second builds the distribution and engine directly inside the call to bind(). Both produce a generator with the same behavior, so the choice comes down to readability.
Practice Tracing the Pipeline
A generator is built with bind(one_to_six, re), and die() is called several times, but every roll is 3. Using the three-step check from this article, which part would you inspect first, and why? Explain what each of the three parts is responsible for supplying.
Hints
- Ask which part supplies the raw value and which part shapes it into the 1 to 6 range.
- The source's check order is the engine declaration, then the distribution arguments, then the
bind()call.
Write the one line that makes die behave as one_to_six(re), using a distribution named one_to_six and an engine named re. Then explain in one sentence which of the two names must come first, and why.
Hints
- The function that makes a callable from another callable and an argument is
bind(). bind()calls its first argument with its second.
Key Takeaways
- A random number generator in the C++ standard library combines an engine, which is the source of randomness, with a distribution, which maps engine output to a specific range.
bind(one_to_six, re)creates a callable named die, so that callingdie()is equivalent to callingone_to_six(re)and produces a roll from 1 to 6.- Only one step in a call to
die()asks the engine for a number; the distribution then shapes that raw value into the required range. - When results go wrong, check the engine declaration, then the distribution arguments, then the
bind()call, in that order. - Build the simplest generator that meets your need first, and use named or compact construction depending on how familiar the parts are.