我认为c ++ 11随机分布(例如uniform_int_distribution
)生成的值仅取决于传递给operator()
的生成器的状态。但是,由于某种原因,const
的签名中没有operator()
说明符。这是什么意思,我应该如何将分布作为函数参数传递?我认为我必须将它作为任何非相互参数传递:通过const引用,但现在我不确定。
答案 0 :(得分:21)
我最初误解了这个问题,然而,现在我明白了,这是一个很好的问题。一些挖掘g ++ <random>
实现的来源给出了以下内容(为清楚起见,省略了一些内容):
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
而某些{ - 1}}为非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
。
答案 1 :(得分:1)
另一个答案是:
这仅是理论上的问题,但我想它之所以不限于const的原因是允许实现者根据需要更改其实现。此外,它保持了更统一的接口-如果某些operator()为const,而某些为非const,则一切都会变得有些混乱。
这在大多数情况下是正确的,但是比通用编程的背景要深得多。
(正如@Calimo所说的,这使const
只是为了“以防万一”而被忽略了。)
考虑这一点后,我得出了一个结论,问题在于以下原则是否可以是 const
的成员函数实际上取决于{{1 }}。
_UniformRandomNumberGenerator
在(通用)规范的这个级别上还不知道,所以只有到那时“ [规范]才允许实现者对[内部状态]进行突变”,并且为了通用起见。
因此,稳定性的问题是在编译时应该知道template<typename _UniformRandomNumberGenerator>
result_type
operator()(_UniformRandomNumberGenerator& __urng)
是否能够生成足够的随机性(位)以进行分布以产生样本绘制。 / p>
在当前规范中,可能性被排除在外,但原则上可以通过具有成员函数的两个互斥版本来实现(或指定):
_UniformRandomNumberGenerator
template<typename _URG, typename = std::enable_if<not has_enough_randomness_for<_URG, result_type>::value > >
result_type
operator()(_UniformRandomNumberGenerator& __urng){..statefull impl..}
template<typename _URG, typename = std::enable_if<has_enough_randomness_for<_URG, result_type>::value > >
result_type
operator()(_UniformRandomNumberGenerator& __urng) const{..stateless impl...}
是一个虚构的布尔元函数,可以判断特定实现是否可以是无状态的。
但是,通常还有另一个障碍,即实现是否是无状态的取决于发行版的运行时参数。 但是由于这是运行时信息,因此不能作为类型系统的一部分传递!
如您所见,这将打开另一罐蠕虫。原则上,has_enough_randomness_for
个分布参数可以检测到这一点,但我完全理解委员会在此停止。
如果您需要一个不变的分布(例如,从概念上讲是正确的),则可以通过支付价格轻松实现:
(1)可能效率很低,(2)可能效率低下,极端难以正确实施。
由于一般来说(2)几乎不可能正确,即使正确,它也会有些效率低下,我只想展示如何实现一个有效的无状态分布:
constexpr
以template<class Distribution>
struct immutable : Distribution{
using Distribution::Distribution;
using Distribution::result_type;
template<typename _URG> result_type operator()(_URG& __urng) const{
auto dist_copy = static_cast<Distribution>(*this);
return dist_copy(__urng);
}
// template<typename _URG> result_type operator()(_URG& __urng) = delete;
};
替代immutable<D>
的方式。 (D
的另一个名称可能是immutable<D>
。)
例如,我已经用conceptual<D>
进行了测试,uniform_real_distribution
的替换速度几乎是它的两倍(因为它可以复制/修改/丢弃标称状态),但是正如您指出的那样,可以使用它如果这对您的设计很重要(据我所知),则可以在更“概念”的上下文中进行。
(另一个不相关的小优点是,您可以在线程之间使用共享的不可变分配)
错误的说明性代码如下:
为了说明这样做有多么困难(2),我将对immutable
进行天真专业化,该专业化对于某些用途几乎是正确的(或者根据您是谁而非常不正确)问。)
immutable<std::uniform_int_distribution>
此无状态实现非常“有效”,但对于template<class Int>
struct immutable<std::uniform_int_distribution<Int>> : std::uniform_int_distribution<Int>{
using std::uniform_int_distribution<Int>::uniform_int_distribution;
using std::uniform_int_distribution<Int>::result_type;
template<typename _URG> result_type operator()(_URG& __urng) const{
return __urng()%(this->b() - this->a()) + this->a(); // never do this ;) for serious stuff, it is wrong in general for very subtle reasons
}
// template<typename _URG> result_type operator()(_URG& __urng) = delete;
};
和a
(分布的限制)的任意值,并非100%正确。
如您所见,对于其他发行版(包括连续发行版),此路径非常困难,棘手且容易出错,因此我不建议这样做。
这主要是个人意见:情况是否可以改善?
是的,但是只有一点。
该发行版可能有两个版本的b
,一个是无operator()
(即const
),这是最佳版本(当前版本),另一个是&
它不能修改什么状态。但是,尚不清楚它们是否必须在确定性上保持一致(即给出相同的答案)。
(即使复制到后备版本,其结果也不会与完整的可变分布相同。)但是,我认为这不是一条可行的道路(同意其他答案)。您可以使用一个不变的版本,也可以同时使用一个不变的版本。
我认为可以做的是拥有一个可变的版本,但要为r值引用(const
)提供特定的重载。
这样,可以使用可变版本的机制,但是可以省略更新(例如,重置)状态的现在“无用”的步骤,因为将不再使用特定实例。这样一来,在某些情况下可以节省一些操作。
这样,上述operator() &&
适配器可以用这种方式编写并利用语义:
immutable