另一个答案是:
这只是理论上的,但我想它不限于 const 的原因是允许实现者在需要时改变他们的实现。此外,它保持了一个更统一的接口——如果一些 operator() 是 const 而一些是非常量,这一切都会变得有点混乱。
这大部分是正确的,但它比泛型编程的上下文更深。
(正如@Calimo 所说,这留下了const 被省略只是“以防万一”的想法)。
考虑到这一点,我得出的结论是,问题转化为以下成员函数原则上 const与否真的取决于_UniformRandomNumberGenerator的实际类型。
template<typename _UniformRandomNumberGenerator>
result_type
operator()(_UniformRandomNumberGenerator& __urng)
在(通用)规范的这个级别上,这是不知道的,所以只有这样“[规范]允许实现者改变[内部状态]”并且为了通用性而这样做。
因此,constness 的问题是在编译时应该知道_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 是一个想象的布尔元函数,可以判断特定实现是否可以是无状态的。
然而,还有一个障碍,一般来说,实现是否是无状态的取决于分布的运行时参数。
但由于这是运行时信息,所以它不能作为类型系统的一部分传递!
如您所见,这又打开了一个蠕虫罐。 constexpr 分布的参数原则上可以检测到这一点,但我完全理解委员会在这里停下来。
如果您需要一个不可变的分布(例如“概念上”正确),您可以通过付出代价轻松实现:
- 每次使用前复制原始分布。
- 以无状态方式自行实现分发逻辑。
(1) 可能非常低效,(2) 它可能有些低效并且极其难以正确实施。
由于(2)一般来说几乎不可能正确,即使正确,它也会有些低效,我只会展示如何实现一个正常工作的无状态分布:
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 对此进行了测试,immutable 替换的速度几乎是原来的两倍(因为它复制/修改/丢弃了标称状态),但正如您指出的那样,它可以用于更多“如果这对您的设计很重要(我可以理解),则为“概念”上下文。
(还有另一个无关紧要的小优势,即您可以跨线程使用共享的不可变分布)
错误但说明性的代码如下:
为了说明实现 (2) 的难度,我将对 immutable<std::uniform_int_distribution> 进行 naive 特化,这对于某些用途几乎是正确的(或者非常不正确,具体取决于您询问的对象。 )
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 和b 的任意值(分布的限制)并非100% 正确。
如您所见,对于其他分布(包括连续分布),这条路径非常困难、棘手且容易出错,因此我不推荐它。
这主要是个人意见:情况可以改善吗?
是的,但只有一点点。
发行版可能有两个版本的operator(),一个没有-const(即&),这是最佳的(当前一个),一个是const,它可以不修改状态.但是,尚不清楚它们是否必须具有确定性一致(即给出相同的答案)。
(即使回退到副本也不会给出与完整的可变分布相同的结果。)。但是,我认为这不是一条可行的道路(同意其他答案);您要么使用不可变版本,要么使用不可变版本,但不能同时使用两者。
我认为可以做的是有一个可变版本,但对 r 值引用有一个特定的重载 (operator() &&)。
这样可以使用可变版本的机制,但是可以省略现在“无用”的更新(例如重置)状态的步骤,因为特定实例将不再被使用。这样在某些情况下可以节省一些操作。
这样,上面描述的immutable 适配器可以这样编写并利用语义:
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 std::move(dist_copy)(__urng);
// or return (Distribution(*this))(__urng);
}
};