Concepts / Binding Function Arguments with std::bind

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.

  • Programming

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?

  • Something entirely new with no connection to functions you already know

  • Calling a function that was built by combining two known parts

Reveal answer
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

Definition

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

QuestionEngineDistribution
What is its job?Source of randomnessMaps engine output to a specific range
What does it produce?Raw values from its own output rangeValues within the range it was configured with, such as 1 to 6
Which name in the die example?reone_to_six
Does it work alone?Not as a die; its raw values are not yet a die rollNot without an engine to draw values from
What to check when results are wrong?The engine declarationThe 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).

cpp
Notes on the code
  • one_to_six is 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.

Engine resupplies a raw valueDistributionone_to_sixmaps raw value to 1 to 6bind(one_to_six, re)callable stored as dieRoll from 1 to 6returned by die()raw valuewired in as first argumentdie() returns
How does a single random number travel from the engine, through the distribution, to the value returned by die()?

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.

Worked example

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.

  1. 1
    Call die()

    The program calls die(). Because die was made by bind, this call is forwarded to its stored target.

  2. 2
    Forward to one_to_six

    The bound object calls one_to_six with the engine re as its argument, which is the same as calling one_to_six(re).

  3. 3
    Request 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.

  4. 4
    Shape into 1 to 6

    The distribution takes the raw value and maps it into the range it was configured with, 1 to 6.

  5. 5
    Return the roll

    The shaped value travels back through die() to the caller, which receives a roll from 1 to 6.

Result

A single call to die() produces one roll from 1 to 6, and the engine is consulted exactly once along the way.

Programcalls die()diebound callableone_to_sixdistributionreenginedie()one_to_six(re)request raw valueraw valueroll from 1 to 6
When die() is called, what functions run and in what order, and which one actually asks the engine for a number?

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.

Generator outputthe value that looks wrongEngine declarationcheck firstDistributionargumentscheck secondbind() callcheck thirdsupplies raw valuessets the rangewires engine and distribution
For each value in the generator's output, which part of the pipeline supplied it, and where could a wrong or repeated value originate?
  • Common mistake

    Looking for the fault in the wrong part of the pipeline

    Why it fails

    If the values are not coming from the engine, checking the distribution arguments wastes time. Each symptom points to a specific part.

    Do this instead

    Ask 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 fails

    bind() calls its first argument with its second. Reversing them asks the wrong thing to do the wrong job.

    Do this instead

    Place 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 fails

    The engine supplies raw values, not values in the 1 to 6 range. Its output is not yet a die roll.

    Do this instead

    Remember 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 fails

    The source notes that for novices the fully general interface can be a serious obstacle. A simple generator is often enough to start.

    Do this instead

    Build 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

MEDIUM

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.
EASY

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 calling die() is equivalent to calling one_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.

Related concepts

More concepts starting with B