【问题标题】:std::function works beautifully with std::bind - but why?std::function 与 std::bind 完美配合 - 但为什么呢?
【发布时间】:2019-11-16 21:40:45
【问题描述】:

我使用std::uniform_int_distribution 来生成素数(p)。我将分发对象放在一个匿名命名空间中——这对于成年人来说似乎是 C++ 的“静态链接”......

namespace
{
    // a more pedantic range: [2, 18446744073709551557]
    std::uniform_int_distribution<uint64_t> p_dist {2};

    std::mt19937 rng; // (sufficient IV states for uniqueness)
}

请注意,我在可移植代码允许的范围内尽可能彻底地播种 Mersenne Twister。不过,这对这个问题并不重要。这只是为了向读者保证我正在正确使用随机工具:

std::seed_seq::result_type data[rng.state_size];
std::random_device rdev;

std::generate_n(data, rng.state_size, std::ref(rdev));
std::seed_seq rng_seed (data, data + rng.state_size);
rng.seed(rng_seed);

这很方便,因为我有一个确定性的u64_prime(p) 函数,使用(7) bases,可以确定(p) 是否为素数:

uint64_t p;
while (!u64_prime(p = p_dist(rng)))
    ;

现在我创建一个std::function 对象:

std::function<uint64_t()> zp_rng = std::bind(
    decltype(p_dist){0, p - 1}, std::ref(rng));

这个函数:zp_rng()可以被调用来返回Z(p)中的一个随机数。也就是说,使用分布对象为:[0, p - 1] 来自引用的 rng 的结果。


现在这非常令人印象深刻 - 但我通过剪切和粘贴有效地采用了它,而对std::function 之间的交互作用以及给std::bind 的参数的交互作用知之甚少.

decltype(p_dist){0, p - 1} 并没有让我感到困惑——这只是说明我们仍想使用std::uniform_int_distribution 的一种方式。我对std::ref(rng) 的理解是,它可以防止 rng 的本地副本被实例化,而是强制使用引用......所以:


:有效确定:dist(rng) 被使用的基本规则是什么 - 我不明白为什么 std::bind 会强制执行这种交互。很多交互似乎都基于 operator () 方法。

Qstd::functioncppreference.com 上被称为“通用多态函数包装器”。那么是不是封装了一个uint64_t返回类型的函数呢?或者再次使用operator () 语法来驱动函数的概念?

尽管这些结构非常有用,但我觉得我在这里某种程度上是货物狂热的编程。我正在寻找一个能以具体方式解决任何歧义并为类似问题增加洞察力的答案 - bind 参数如何交互,function 签名如何反映这一点?


我没有收到任何关于使用 std::bind 的积极反馈。很多关于简单使用 lambda 函数的优越结果(和代码生成),即使在这样一个简单的情况下。我自己的测试验证了这一点。

【问题讨论】:

  • 不是一个答案,而是一个提示:std::function 在这里太过分了,您可以使用 lambda 代替,这也可以让您摆脱 std::bind :-)
  • @Acorn - 你可能是对的,但在这种情况下,在理解底层机制之前,我不禁觉得 lambda 会是另一层混淆。
  • 我不太明白这些问题。你基本上是在问std::bindstd::function 是什么?
  • Lambdas FTW \o/
  • here 实际上是一个骗子。我不想基本上重写那些很好的答案。我确实想让你了解情况。

标签: c++ c++11 c++17 std-function stdbind


【解决方案1】:

std::bindstd::function 无关,它所做的只是包装一些可调用的东西,以便始终传递一组参数,请参阅https://en.cppreference.com/w/cpp/utility/functional/bind

std::function 只接受任何符合您指定的函数签名的可调用对象作为其模板参数。

std::uniform_int_distribution 是可调用的,因为它指定了 operator(),请参阅 https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution/operator() 。这特别需要一个发电机。我们使用 bind 围绕它创建一个包装器,这样我们就不必总是显式地传递生成器。然后将结果存储在与 uint64_t 返回类型匹配且没有参数的std::function 中(因为绑定了生成器参数)。

如果这个概念对你来说是陌生的,你应该阅读运算符重载,请参阅https://en.cppreference.com/w/cpp/language/operators(特别是函数调用运算符)。

【讨论】:

  • 正如我所说的 - 不急于成为第一。您已经提供了一些概念性要点,并且可以详细说明赏金。仅仅因为 std::uniform_int_distribution 是可调用的,因为它有一个 operator () 并不能真正解释为什么使用 rng 绑定。如果绑定中还有其他对象等怎么办?
  • std::bind 只是创建一个包装函数,该函数始终传递绑定参数 - 在您的情况下为 rng 。它只是另一个函数对象。
【解决方案2】:

问: 有效确定的基本规则是什么:使用 dist(rng) - 我不明白为什么 std::bind 会强制执行这种交互。许多交互似乎都基于 operator () 方法。

std::bind 执行function composition。第一个参数必须是函数对象,即 callable 之类的函数(例如普通函数,或具有重载 operator() 的类)。

std::bind 的调用会复制其参数,将参数的副本“绑定”到第一个参数(函数对象),并返回一个新的函数对象,该对象将调用函数对象的副本。

所以在一个简单的情况下:

int f(int i) { return i; }
auto f1 = std::bind(f, 1);

这将值1 绑定到函数f,创建一个可以不带参数调用的新函数对象。当您调用 f1() 时,它将使用参数 1 调用 f,即它将调用 f(1),并返回返回的任何内容(在本例中只是 1)。

std::bind(f, 1) 返回的事物的实际类型是一些特定于实现的类类型,可能称为std::__detail::__bind_type&lt;void(*)(int), int&gt;。您并不打算直接引用该类型,您可以使用auto 捕获对象或将其存储在不关心精确类型的其他地方,因此:

auto f1 = std::bind(f, 1);

或:

std::function<int()> f1 = std::bind(f, 1);

在更复杂的情况下,当您调用 std::bind(decltype(p_dist){0, p - 1}, std::ref(rng))) 时,您将获得一个新函数对象,其中包含临时 decltype(p_dist){0, p - 1} 的副本和由 std::ref(rng) 创建的 reference_wrapper&lt;std::mt19937&gt; 的副本。当您调用该新函数对象时,它会调用包含的分布,并将对 rng 的引用传递给它。

也就是说它可以不带参数调用,它会用包含的随机引擎调用包含的随机数分布,并返回结果。

问: std::function 在 cppreference.com 上被称为“通用多态函数包装器”。那么是不是封装了一个uint64_t返回类型的函数呢?

std::function&lt;uint64_t()&gt; 是一个函数对象的包装器,它可以不带参数调用并返回一个uint64_t(或隐式可转换为uint64_t 的东西)。它可用于存储可复制构造的任意函数对象的副本,并且可以不带参数调用,并返回可转换为 uint64_t 的内容。

(更一般地说,std::function&lt;R(A1, A2 ... AN)&gt; 是一个函数对象的包装器,当使用 N 个参数调用时返回 R,类型为 A1A2 ... AN。)

由于std::bind 调用的结果是一个可复制构造函数对象,它可以不带参数调用并返回uint64_t,因此您可以将std::bind 调用的结果存储在std::function&lt;uint64_t()&gt; 中,并且当您调用function,它将调用bind 调用的结果,这将调用包含的分布,并返回结果。

或者再次使用 operator() 语法来驱动函数的概念?

我不确定这是什么意思,但总的来说,在 C++ 中我们经常谈论“函数对象”或“可调用对象”,它们是函数的泛化,即可以使用函数调用语法调用的东西,例如a(b, c)

【讨论】:

    【解决方案3】:

    在现代 C++ 中无需使用 std::bind - 改为使用 lambda:

    // NOTE: p and rng are captured by reference and must outlive zp_rng!
    std::function<uint64_t()> zp_rng = [&]() { return decltype(p_dist){0, p - 1}(rng); };
    

    对于std::bindstd::function实例,它们是可调用的函数对象,即它们有operator()

    【讨论】:

    • 这个语法对我来说实际上更有意义。 distrng 的操作是显式的。但是zp_rng真的可以被认为是一个lambda吗,仅仅因为它的实现是?
    • 实际上,std::bind 及其“占位符”语法的使用让我想起了可变参数模板之前最糟糕的黑客攻击......
    • @Brett Hale zp_rngstd::function 的一个实例,而不是 lambda。 lambda 在= 的右侧。这是可能的,因为function 可以使用任何可调用对象进行初始化,包括 lambda、“binder”、函数指针、自定义函数对象——只要它们具有兼容的 operator()
    • @BrettHale 占位符语法不是可变参数模板之前的黑客攻击。 std::bind()可变参数模板。
    • @BrettHale 嗯?什么世界中最糟糕的?您不确定std::bind 究竟是什么,是造成混乱的根源吗?
    【解决方案4】:

    我引用A Tour of C++ bind()是什么:

    函数适配器接受一个函数作为参数并返回一个函数 可用于调用原始函数的对象。标准 库提供 bind()mem_fn() 适配器来做参数 绑定,也称为柯里化部分评估。粘合剂是 过去大量使用,但大多数用途似乎更容易 使用 lambdas 表示。

    double cube(double);
    auto cube2 = bind(cube,2);
    

    调用cube2() 将使用参数2 调用cube,即, cube(2)

    bind()可以直接使用,可以用来初始化一个 自动变量。其中,bind() 类似于 lambda。

    如果我们想将bind() 的结果赋值给一个变量 具体类型,我们可以使用标准库类型function。一种 function 指定了特定的返回类型和特定的 参数类型。

    标准库function 是一种可以容纳任何你想要的对象的类型 可以使用 调用运算符 () 调用。也就是说,类型的对象 functionfunction object

    所以std::function 是一个函数对象。 函数对象用于定义可以像函数一样调用的对象。 这是一个函数对象的例子:

    template<typename T>
    class Cube2 {
    const T val; // value to compare against
    public:
    Cube2 (const T& v) :val(v) { }
    double operator()(const T& x) const { return pow(x,3); } // call operator
    };
    

    auto cube2 = bind(cube,2); 就像说Cube2&lt;int&gt; cube2{2};

    【讨论】:

      猜你喜欢
      • 2016-12-18
      • 1970-01-01
      • 1970-01-01
      • 2015-05-23
      • 1970-01-01
      • 2015-09-07
      • 2018-11-29
      • 2013-04-16
      相关资源
      最近更新 更多