Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does creating a temporary instance not work in this case?

Tags:

c++

c++11

I've been learning C++11 through Bjarne Stroustrup's "A Tour of C++".

I have the following snippet of code

#include <random>
#include <functional>

int main(int argc, char *argv[])
{
    using namespace std;
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution {1,6};
    distribution(generator); //Case 1 works

    auto die = bind(uniform_int_distribution<>{1,6},default_random_engine{});  //Case 2 works

    distribution(std::default_random_engine{});     //Case 3 Compiler error 
}

Case 3 is my own creation while case 2 comes from the book and case 1 I adapted from elsewhere. Why does case 3 produce the compiler error below? As far as I understand, the only difference between case 1 and case 3 is that I'm using a temporary instance of std::default_random_engine and this temporary instance seems to work in case 2 What am I missing?

Error output:

random.cpp: In function ‘int main(int, char**)’:
random.cpp:13:46: error: no match for call to ‘(std::uniform_int_distribution<int>) (std::default_random_engine)’
     distribution(std::default_random_engine{});     //Case 3 Compiler error
                                              ^
In file included from /usr/include/c++/6.3.1/bits/random.h:35:0,
                 from /usr/include/c++/6.3.1/random:49,
                 from random.cpp:1:
/usr/include/c++/6.3.1/bits/uniform_int_dist.h:164:2: note: candidate: std::uniform_int_distribution<_IntType>::result_type std::uniform_int_distribution<_IntType>::operator()(_UniformRandomNumberGenerator&) [with _UniformRandomNumberGenerator = std::linear_congruential_engine<long unsigned int, 16807ul, 0ul, 2147483647ul>; _IntType = int; std::uniform_int_distribution<_IntType>::result_type = int] <near match>
  operator()(_UniformRandomNumberGenerator& __urng)
  ^~~~~~~~
/usr/include/c++/6.3.1/bits/uniform_int_dist.h:164:2: note:   conversion of argument 1 would be ill-formed:
random.cpp:13:23: error: invalid initialization of non-const reference of type ‘std::linear_congruential_engine<long unsigned int, 16807ul, 0ul, 2147483647ul>&’ from an rvalue of type ‘std::default_random_engine {aka std::linear_congruential_engine<long unsigned int, 16807ul, 0ul, 2147483647ul>}’
     distribution(std::default_random_engine{});     //Case 3 Compiler error
                       ^~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/6.3.1/bits/random.h:35:0,
                 from /usr/include/c++/6.3.1/random:49,
                 from random.cpp:1:
/usr/include/c++/6.3.1/bits/uniform_int_dist.h:169:2: note: candidate: template<class _UniformRandomNumberGenerator> std::uniform_int_distribution<_IntType>::result_type std::uniform_int_distribution<_IntType>::operator()(_UniformRandomNumberGenerator&, const std::uniform_int_distribution<_IntType>::param_type&) [with _UniformRandomNumberGenerator = _UniformRandomNumberGenerator; _IntType = int]
  operator()(_UniformRandomNumberGenerator& __urng,
  ^~~~~~~~
/usr/include/c++/6.3.1/bits/uniform_int_dist.h:169:2: note:   template argument deduction/substitution failed:
random.cpp:13:46: note:   candidate expects 2 arguments, 1 provided
     distribution(std::default_random_engine{});     //Case 3 Compiler error
                                              ^
like image 426
Avatar33 Avatar asked Jan 30 '26 07:01

Avatar33


1 Answers

distribution(generator);

distribution modifies the state of generator, and extracts a value. On the next call, it returns a different value, using the same generator

auto die = bind(uniform_int_distribution<>{1,6},default_random_engine{});

Here, we copy both a uniform_int_distribution<>{1,6} and a default_random_engine{} into the bind function object. On () it passes the default_random_engine{} to the uniform_int_distribution<>{1,6} via operator(). The uniform_int_distribution<>{1,6} modifies the state of the default_random_engine{} and on the next call, it returns a different value, as it uses the (now modified) generator.

distribution(std::default_random_engine{});

here you attempt to bind a random_engine& to a temporary object. The distribution wants to modify the random engine's state, which is why it is taken by reference.

By passing in a temporary, any such change would be discarded instantly. It is easy to do this accidentally and screw up, so C++ by default does not let you bind a temporary object to a non-const lvalue reference.

The designers of C++ std wanted this error, so they took their distribution by non-const lvalue reference. It is trying to tell you "this is a bad plan".

You can work around it:

template<class T>
T& as_lvalue(T&& t){ return t; }

now

distribution(as_lvalue(std::default_random_engine{}));

fools the compiler into thinking that the default_random_engine passed in is not a temporary. It still goes away at the end of the line and the modification to the state of the generator, carefully done by the distribution, is discarded.

This is usually not what you want when generating pseudo-random objects, which is what C++11's random engine is about.

In short, you are not permitted to bind temporaries to non-constant lvalue references. When you do so, it is an error. When you have a parameter that is intended to be both read from and written to, and the written value is important, it is taken by non-constant lvalue reference, in order to prevent you from casually passing in a temporary and having the data written to it lost.

Generators are stateful. You want to keep one around and repeatedly apply a distribution to it.

like image 64
Yakk - Adam Nevraumont Avatar answered Feb 01 '26 22:02

Yakk - Adam Nevraumont



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!