Я думал, что значение, генерируемое случайным распределением С++ 11 (например, t20 > ), зависит только от состояния генератора, которое передается в operator()
. Однако по какой-либо причине в сигнатуре operator()
нет спецификатора const
. Что это значит и как передать распределение в качестве параметра функции? Я думал, что мне нужно передать его как любой параметр без взаимных ссылок: по ссылке const, но теперь я не уверен.
Почему переменные случайных распределений С++ 11 изменяются?
Ответ 1
Сначала я неправильно понял этот вопрос, но теперь, когда я понимаю, это хороший вопрос. Некоторое копание в источнике реализации <random>
для g++ дает следующее (с ясностью осталось несколько бит):
template<typename _IntType = int>
class uniform_int_distribution
{
struct param_type
{
typedef uniform_int_distribution<_IntType> distribution_type;
explicit
param_type(_IntType __a = 0,
_IntType __b = std::numeric_limits<_IntType>::max())
: _M_a(__a), _M_b(__b)
{
_GLIBCXX_DEBUG_ASSERT(_M_a <= _M_b);
}
private:
_IntType _M_a;
_IntType _M_b;
};
public:
/**
* @brief Constructs a uniform distribution object.
*/
explicit
uniform_int_distribution(_IntType __a = 0,
_IntType __b = std::numeric_limits<_IntType>::max())
: _M_param(__a, __b)
{ }
explicit
uniform_int_distribution(const param_type& __p)
: _M_param(__p)
{ }
template<typename _UniformRandomNumberGenerator>
result_type
operator()(_UniformRandomNumberGenerator& __urng)
{ return this->operator()(__urng, this->param()); }
template<typename _UniformRandomNumberGenerator>
result_type
operator()(_UniformRandomNumberGenerator& __urng,
const param_type& __p);
param_type _M_param;
};
Если мы прощупываем все _
, мы видим, что он имеет только один параметр-член, param_type _M_param
, который сам по себе является просто вложенной структурой, удерживающей 2 интегральных значения - по сути, диапазон. operator()
объявляется здесь, а не определяется. Еще одно копание приводит нас к определению. Вместо публикации здесь всего кода, который довольно уродлив (и довольно длинный), достаточно сказать, что внутри этой функции ничего не мутируется. Фактически, добавление const
в определение и объявление с удовольствием скомпилируется.
Затем возникает вопрос, верно ли это для каждого другого распределения? Ответ - нет. Если мы посмотрим на реализацию для std::normal_distribution
, мы найдем:
template<typename _RealType>
template<typename _UniformRandomNumberGenerator>
typename normal_distribution<_RealType>::result_type
normal_distribution<_RealType>::
operator()(_UniformRandomNumberGenerator& __urng,
const param_type& __param)
{
result_type __ret;
__detail::_Adaptor<_UniformRandomNumberGenerator, result_type>
__aurng(__urng);
//Mutation!
if (_M_saved_available)
{
_M_saved_available = false;
__ret = _M_saved;
}
//Mutation!
Это всего лишь теоретизация, но я полагаю, что причина, по которой она не ограничена const
, заключается в том, чтобы позволить исполнителям мутировать их реализацию, если это необходимо. Кроме того, он поддерживает более равномерный интерфейс - если некоторые operator()
равны const
, а некоторые - не const
, все становится немного беспорядочным.
Однако, почему они не просто сделали их const и пусть разработчики используют mutable
, я не уверен. Наверное, если кто-то здесь не был вовлечен в эту часть усилий по стандартизации, вы не можете получить хороший ответ на этот вопрос.С >
Изменить: Как отметил MattieuM, mutable
и несколько потоков не играют хорошо вместе.
Как второстепенно интересно, std::normal_distribution
генерирует сразу два значения, кешируя один (следовательно, _M_saved
). operator<<
, который он определяет, фактически позволяет увидеть это значение перед следующим вызовом operator()
:
#include <random>
#include <iostream>
#include <chrono>
std::default_random_engine eng(std::chrono::system_clock::now().time_since_epoch().count());
std::normal_distribution<> d(0, 1);
int main()
{
auto k = d(eng);
std::cout << k << "\n";
std::cout << d << "\n";
std::cout << d(eng) << "\n";
}
Здесь выходной формат mu sigma nextval
.