Подтвердить что ты не робот

Почему переменные случайных распределений С++ 11 изменяются?

Я думал, что значение, генерируемое случайным распределением С++ 11 (например, t20 > ), зависит только от состояния генератора, которое передается в operator(). Однако по какой-либо причине в сигнатуре operator() нет спецификатора const. Что это значит и как передать распределение в качестве параметра функции? Я думал, что мне нужно передать его как любой параметр без взаимных ссылок: по ссылке const, но теперь я не уверен.

4b9b3361

Ответ 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.