C++标准库随机数引擎种子类型的跨平台确定方法问询
最近在Reddit的C++板块,大伙聊起了标准库随机数使用的各种坑,其中一个挺让人头疼的问题就是——怎么正确给随机数引擎播种,特别是std::seed_seq对应的输入类型,在不同STL实现里居然不一样!
比如这段大家常用的播种代码,里面的XXXXXXXXX类型就成了跨平台的卡点:
std::random_device r; std::array<XXXXXXXXX, std::mt19937::state_size> seed; std::generate_n(seed.data(), seed.size(), std::ref(r)); std::seed_seq seq(std::begin(seed), std::end(seed));
为啥会有这个问题?看看两大主流STL实现的内部代码就懂了:
MSVC实现(Visual Studio 2022 17.14.0)
它的梅森旋转引擎seed方法里,用的是unsigned long作为种子数组的类型,大小依赖架构(32位或64位):
template <class _Seed_seq, _Enable_if_seed_seq_t<_Seed_seq, mersenne_twister_engine> = 0> void seed(_Seed_seq& _Seq) { // reset sequence from seed sequence constexpr int _Kx = (_Wx + 31) / 32; unsigned long _Arr[_Kx * _Nx]; _Seq.generate(&_Arr[0], &_Arr[_Kx * _Nx]); }
GNU libstdc++实现
它则用了std::uint_least32_t,这个类型至少是32位,具体大小看系统对该类型的定义:
template<typename _UIntType, size_t __w, size_t __n, size_t __m, size_t __r, _UIntType __a, size_t __u, _UIntType __d, size_t __s, _UIntType __b, size_t __t, _UIntType __c, size_t __l, _UIntType __f> template<typename _Sseq> auto mersenne_twister_engine<...>::seed(_Sseq& __q) -> _If_seed_seq<_Sseq> { const _UIntType __upper_mask = (~_UIntType()) << __r; const size_t __k = (__w + 31) / 32; uint_least32_t __arr[__n * __k]; __q.generate(__arr + 0, __arr + __n * __k); }
那问题来了:XXXXXXXXX到底该填什么类型,有没有跨平台的编程式确定方法?
跨平台解决方案其实很简单
咱先回到C++标准的规定:标准只要求std::seed_seq的generate方法接受的迭代器指向无符号整数类型,且该类型至少能容纳32位的值(毕竟mt19937是32位的梅森旋转引擎),但具体类型交给STL实现自行决定。
基于这个规定,有这几个靠谱的解决办法:
直接用
std::uint_least32_t作为通用类型
这个类型是标准定义的“至少32位的最小无符号整数类型”,完美适配两大主流实现:- 对MSVC来说,
unsigned long可以安全接收uint_least32_t的隐式转换值 - 对GNU libstdc++来说,内部直接用的就是这个类型,完全匹配
修改后的代码如下:
#include <random> #include <array> #include <algorithm> std::random_device r; std::array<std::uint_least32_t, std::mt19937::state_size> seed; std::generate_n(seed.data(), seed.size(), std::ref(r)); std::seed_seq seq(std::begin(seed), std::end(seed)); // 然后用seq初始化mt19937 std::mt19937 engine(seq);- 对MSVC来说,
用动态容器替代固定数组
如果你不想纠结固定大小的数组,也可以用std::vector动态生成种子序列,逻辑完全一致:#include <random> #include <vector> #include <algorithm> std::random_device r; std::vector<std::uint_least32_t> seed(std::mt19937::state_size); std::generate(seed.begin(), seed.end(), std::ref(r)); std::seed_seq seq(seed.begin(), seed.end()); std::mt19937 engine(seq);有没有办法自动推导内部类型?
遗憾的是,C++标准并没有提供对应的类型 trait来直接获取STL实现内部的种子类型——这属于实现细节,标准不要求暴露。当然你可以用SFINAE写一个检测工具,判断某个类型是否能被当前实现的seed_seq::generate接受,但其实完全没必要,std::uint_least32_t已经能覆盖所有符合标准的STL实现了。
内容来源于stack exchange

