C++ 提供给std::generate的函子是否可以是有状态的?

C++ 提供给std::generate的函子是否可以是有状态的?,c++,C++,最近我读到一些STL算法有未定义的行为,如果传递的函子是有状态的(有内部副作用)。我使用了std::generate函数,其函子与以下类似(不那么简单): class Gen { public: explicit Gen(int start = 0) : next(start) { } int operator() () { return next++; } private: int next; };

最近我读到一些STL算法有未定义的行为,如果传递的函子是有状态的(有内部副作用)。我使用了
std::generate
函数,其函子与以下类似(不那么简单):

class Gen
{
public:
    explicit Gen(int start = 0)
        : next(start)
    {
    }

    int operator() ()
    {
        return next++;
    }

private:
    int next;
};
std::generate
一起使用是否安全?生成值的顺序有保证吗


编辑:这里的声明几乎每个有意义的生成器都是有状态的。想想看:生成器调用没有参数。如果生成器是无状态的,那么每次都必须生成相同的值。换句话说,唯一的无状态生成器是常量生成器:

template <typename T>
struct constant {
    constant(T&& value) : value{value} {}

    T const& operator ()() const { return value; }

private:
    T const value;
};
模板
结构常数{
常量(T&&value):值{value}{}
T const&运算符()()const{return value;}
私人:
T常数值;
};
或者,您也可以有一个生成器,它的状态在对象本身之外,例如。但实际上只有这两种情况适用。这将是非常严格的限制。因此,是的,有状态生成器是绝对可能的

您提到的这个语句的来源是而不是谈论生成器–相反,它谈论的是谓词,它是一个不同的函数类,具有不同的要求(谓词是一个带有单个参数的函数,返回
bool
值)。

简介 将有状态函子与std::generate等函数一起使用是没有问题的,但必须小心不要遇到以下问题:底层实现生成的副本将以不同于开发人员所想的方式更改程序的语义

25.1p10
算法库-常规
[Algorithms.General]

(注:除非另有规定,允许以函数对象为参数的算法可以自由地复制那些函数Objcts。对于对象身份不匹配的程序员应该考虑使用指向非复制的实现对象的包装类,例如RealthySuxPror或一些等价的解决方案。”


分配/评估顺序 该标准明确规定,将对生成器进行精确的
last-first
(N)赋值和调用,但没有说明顺序。更多信息可在以下问答中阅读:


有状态函子+
std::generate
,不安全? 通常不会,但有几个注意事项

在std::generate中,标准保证为给定范围内的每个元素调用相同的functor类型实例,但是正如std::generate声明所暗示的那样,我们可能会遇到这样的问题:我们忘记了传递的functor在
内部调用了
,函数将是作为参数传递的函数的副本

请参阅下面的代码段,其中我们声明了一个函子以生成“唯一”ID:


备选方案2

当然,你也可以通过写一些像下面这样令人困惑的东西来增强你的手指的力量:

std::generate<decltype(vec1.begin()), id_generator<int>&>(vec1.begin(), vec1.end(), gen);
std::generate(vec1.begin(),vec1.end(),gen);

就标准而言,您没有任何保证。一 实现可以随时复制功能对象。 这一想法是,一个实现可能会划分 将范围划分为子范围,并以单独的方式处理每个子范围 线(至少,这是在一段时间内所声称的 标准化过程。)

当然,对于某些算法,例如
generate
累积
,允许多个副本或重新排序将使 这个算法毫无用处。通常,这样的算法可以保证 秩序;他们似乎忘记了这一点。及 虽然标准似乎忘记了允许复制, 实际上,如果订单得到保证,就没有必要再订购了 正在处理多个副本,因此我认为我们可以安全地 假设没有,即使没有严格的
保证。

你在哪里读到的?我不认为有任何限制,除了它必须是无参数可调用的,并产生指定类型的结果。@luk32更新为添加link我们是在谈论传递值(Scott Meyers Effective stl)吗?请参阅标准库算法按值获取函数对象,它们的实现允许进行复制。使用
std::ref
将函数对象转换为具有引用语义的对象。这允许您传递状态——非常有用,例如,对于随机数生成器(例如,
std::generate(v.begin()、v.end()、std::ref(rng))
)。它可以使用一些全局/静态变量来保持其状态,但这确实很糟糕。为了证明我不是在吹毛求疵地发明东西,current使用了
std::rand
,它显然有一个状态,是一个函数,所以没有其他方法来实现它。@luk32哦,我实际上也把它算作有状态的——但你是对的,这是一个重要的区别,谢谢你提供的信息。元素的顺序如何?@NeilKirk没有指定为元素分配生成值的顺序,但标准要求调用传递的生成器
last-first
次,其中last和first是传递的迭代器。它还要求进行精确的
last-first
赋值。因此,我的示例无法创建一个递增的数字数组?@NeilKirk我必须更深入地了解该标准,但现在还不能保证函子的第一次调用将分配给范围中的第一个元素。我会尽快给你回复。@NeilKirk可能会的,但据我所知,除了每个的
,没有订单保证(事实上,这样的保证对于
int main () {
  id_generator<int> gen;

  std::vector<int>  vec1 (5);
  std::vector<int>  vec2 (5);

  std::generate (vec1.begin (), vec1.end (), gen);
  std::generate (vec2.begin (), vec2.end (), gen);

  std::cout << gen.next_id () << std::endl; // will print '1'
}
std::generate (vec1.begin (), vec1.end (), std::ref (gen));
std::generate (vec2.begin (), vec2.end (), std::ref (gen));

std::cout << gen.next_id () << std::endl; // will print '11'
std::generate<decltype(vec1.begin()), id_generator<int>&>(vec1.begin(), vec1.end(), gen);