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

Случайный порядок чисел в С++ с использованием <random>

У меня есть следующий код, который я написал для проверки части более крупной программы:

#include <fstream>
#include <random>
#include <iostream>
using namespace std ;

int main()
{
  mt19937_64 Generator(12187) ;
  mt19937_64 Generator2(12187) ;
  uniform_int_distribution<int> D1(1,6) ;

  cout << D1(Generator) << " " ;
  cout << D1(Generator) << " " << D1(Generator) << endl ;
  cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;

  ofstream g1("g1.dat") ;
  g1 << Generator ;
  g1.close() ;
  ofstream g2("g2.dat") ;
  g2 << Generator2 ;
  g2.close() ;
}                                                            

Два генератора засеяны одним и тем же значением, поэтому я ожидал, что вторая строка на выходе будет идентична первой. Вместо этого вывод

1 1 3
1 3 1

Состояние двух генераторов, напечатанных в файлах *.dat, одинаковое. Мне было интересно, может ли быть какая-то скрытая многопоточность в генерации случайных чисел, вызывающая несоответствие порядка.

Я скомпилировал с g++ версии 5.3.0, в Linux, с флагом -std=c++11.

Заранее благодарим за помощь.

4b9b3361

Ответ 1

x << y является синтаксическим сахаром для вызова функции operator<<(x, y).

Вы помните, что стандарт С++ не накладывает ограничений на порядок оценки аргументов вызова функции.

Таким образом, компилятор может испускать код, который сначала вычисляет x или y.

Из стандарта: §5 примечание 2:

Операторы могут быть перегружены, то есть заданы значения при применении к выражениям типа класса (пункт 9) или тип перечисления (7.2). Использование перегруженных операторов преобразуется в вызовы функций, как описано в 13,5. Перегруженные операторы подчиняются правилам синтаксиса, указанным в пункте 5, но требования тип операнда, категория значения, и порядок оценки заменяются правилами вызова функции.

Ответ 2

Это потому, что порядок оценки этой строки

cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;

не то, что вы думаете.

Вы можете проверить это следующим образом:

int f() {
  static int i = 0;
  return i++;
}

int main() {
  cout << f() << " " << f() << " " << f() << endl ;
  return 0;
}

Выход: 2 1 0


Заказ не указан стандартом С++, поэтому порядок может отличаться для других компиляторов, см. ответ Ричарда Ходжеса.

Ответ 3

Небольшое изменение в программе показывает, что происходит:

#include <fstream>
#include <random>
#include <iostream>
using namespace std ;

int main()
{
  mt19937_64 Generator(12187) ;
  mt19937_64 Generator2(12187) ;
  uniform_int_distribution<int> D1(1,100) ;

  cout << D1(Generator) << " " ;
  cout << D1(Generator) << " " ;
  cout << D1(Generator) << endl ;
  cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;
}

Вывод:

4 48 12
12 48 4

Итак, ваши Генераторы производят равные результаты - но порядок аргументов вашей cout-линии вычисляется в другом порядке.

Попробуйте онлайн: http://ideone.com/rsoqDe

Ответ 4

Эти строки

  cout << D1(Generator) << " " ;

  cout << D1(Generator) << " "
       << D1(Generator) << endl ;

  cout << D1(Generator2) << " "
       << D1(Generator2) << " "
       << D1(Generator2) << endl ;

потому что D1() возвращает int, для которого ostream::operator<<() имеет перегрузку, эффективно вызывает (исключая endl)

cout.operator<<(D1(Generator));

cout.operator<<(D1(Generator))
    .operator<<(D1(Generator));

cout.operator<<(D1(Generator2))
    .operator<<(D1(Generator2))
    .operator<<(D1(Generator2));

Теперь стандарт должен сказать,

& раздел; 5.2.2 [4]

Когда вызывается функция, каждый параметр должен инициализируется соответствующим аргументом.

[Примечание: такие инициализации неопределенно упорядочены относительно друг друга - примечание конца)

Если функция нестатическая функции-члена, этот параметр функции должен быть равен инициализируется указателем на объект вызова

Итак, разделим предыдущее выражение

cout.operator<<(a())  // #1
    .operator<<(b())  // #2
    .operator<<(c()); // #3

Чтобы проиллюстрировать конструкцию указателя this, они концептуально эквивалентны (опуская ostream:: для краткости):

operator<<(           // #1
  &operator<<(        // #2
    &operator<<(      // #3
      &cout,
      a()
    ),                // end #3
    b()
  ),                  // end #2
  c()
);                    // end #1

Теперь посмотрим на вызов верхнего уровня. Что мы оцениваем сначала, #2, или c()? Поскольку, как подчеркивается в цитате, порядок неопределен, тогда мы не знаем &mdash, и это верно рекурсивно: даже если бы мы оценили #2, мы все равно столкнулись бы с вопросом, следует ли оценивать его внутренний #3 или b().

Так что, надеюсь, объясняет, что происходит здесь более четко.