Continuing with our emphasis on learning the standard library components of C++, we shall now look at functions and classes that provide us a means to generate random numbers.

Before C++11 (and it's successor C++14) became widely adopted, the standard way to do this was to use the `srand` and `rand` functions from <cstdlib>. You may be familiar with this older method, but I will not discuss it any further here because it uses standard library components from an older and cruder programming language. You should become accustomed to using the newer method especially designed for the C++ of the 21st century.

The modern way of generating random numbers in C++ is to use the components found in <random>. Here is the simplest way to generate a random integer:

```
#include <iostream>
#include <random>
using namespace std;
int main()
{
cout << random_device{}() << endl;
return 0;
}
```

The data type `random_device` represents a non-deterministic random integer generator. The expression `random_device{}` creates a value of that data type, then the function call operator (denoted by the empty parenthesis) generates a random integer from this value.

The code in main has the same effect as the following:

```
random_device rd;
cout << rd() << endl;
```

For the purposes of this example, storing an instance of `random_device` in a variable is not needed since we are only using it once.

The `random_device` data type is not usually used by itself, but used in conjuction with pseudo-random number generators and random number distributions.

Here is a program that displays a random integer in the range of 1 to 20:

```
#include <iostream>
#include <random>
using namespace std;
int main()
{
default_random_engine prng(random_device{}());
uniform_int_distribution<int> idist(1, 20);
cout << idist(prng) << endl;
return 0;
}
```

`default_random_engine` represents a pseudo-random number generator (PRNG). A PRNG generates numbers that seem random but are actually deterministic. Every PRNG must be initialized with a "seed" which is just an integer value. From the initial seed, a PRNG will recursively transform this value to give you the next number from the deterministic number sequence.

The reason we use a PRNG instead of `random_device` is because PRNGs have faster performance. For many operating systems, `random_device` is associated with a special file named `/dev/random` which is where data from environmental noise is accumulated into. Not only is reading from a file slower than performing a simple calculation, but `/dev/random` can be exausted when too much data is extracted from it, in which case reading from it will block until more data becomes available.

Therefore, the statement `default_random_engine prng(random_device{}());` creates a PRNG variable initialized with a single seed value taken from an instance of `random_device`. Other than the way you initialize it, `default_random_engine` can be used the same way you would use a `random_device`: the function call operator generates the next random integer.

The RNGs and PRNGs in C++ only generate random integers from 0 to approximately 4.2 billion. To generate random numbers in a specific range we use a PRNG or RNG together with a random number distribution.

`uniform_int_distribution` distributes an integer generator's output over a uniform, closed interval. Such a data type is initialized with the minimum and maximum values of the interval and the function call operator will generate a value within this interval from a given integer generator.

Creating a `default_random_engine` should only happen once in your program, then you may use it multiple times for different random number distributions. Here is a program that generates 5 random integers from -100 to 100 and 5 random real numbers from -20.0 to 0.5:

```
#include <iostream>
#include <random>
using namespace std;
int main()
{
default_random_engine prng(random_device{}());
uniform_int_distribution<int> idist(-100, 100);
uniform_real_distribution<double> ddist(-20.0, 0.5);
for (int i = 0; i < 5; ++i)
cout << idist(prng) << ' ';
cout << endl;
for (int i = 0; i < 5; ++i)
cout << ddist(prng) << ' ';
cout << endl;
return 0;
}
```

`uniform_int_distribution` and `uniform_real_distribution` distributes random data into an integer range or a floating-point range respectively. The reason we must specify `int` or `double` to qualify these data types is because C++ supports different variations of integers and floating-point data types which you may look into on your own if you're interested.

`random_device`, `default_random_engine`, `uniform_int_distribution`, and `uniform_real_distribution` should be everything you need to start generating random numbers. If you look into what else <random> has to offer you will see that it has other types of PRNGs other than `default_random_engine` and other types of random number distributions that are more mathematically biased than the uniform distributions.