std::function 与 std::bind 配合得很好——但为什么呢?

std::function works beautifully with std::bind - but why?

我正在使用 std::uniform_int_distribution 生成素数 (p)。我将分发对象放在一个匿名命名空间中——这对于成年人来说似乎是 C++ 'static linkage'...

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 播种。但这对问题来说并不重要。这只是为了确保 reader 我正确使用了随机工具:

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() 可以调用 return Z(p) 中的随机数。也就是说,使用分布对象: [0, p - 1] 从引用的结果 rng.


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

的参数

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


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

Qstd::functioncppreference.com 上被称为 'a general-purpose polymorphic function wrapper'。那么是不是封装了一个uint64_treturn类型的函数呢?或者再次使用 operator () 语法来驱动函数的概念?

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


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

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 return 类型且没有参数的 std::function 中(因为生成器参数已绑定)。

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

在现代 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::bind and std::function个实例,它们是可调用的函数对象,即它们有operator().

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

A function adaptor takes a function as argument and returns a function object that can be used to invoke the original function. The standard library provides bind() and mem_fn() adaptors to do argument binding, also called Currying or partial evaluation. Binders were heavily used in the past, but most uses seem to be more easily expressed using lambdas.

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

A call cube2() will invoke cube with the argument 2, that is, cube(2)

A bind() can be used directly, and it can be used to initialize an auto variable. In that, bind() resembles a lambda.

If we want to assign the result of bind() to a variable with a specific type, we can use the standard library type function. A function is specified with a specific return type and a specific argument type.

The standard library function is a type that can hold any object you can invoke using the call operator (). That is, an object of type function is a function 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<int> cube2{2};

Q: What are the basic rules that effectively determine: dist(rng) being used - I don't see why std::bind would enforce this interaction. A lot of interactions seem based around operator () methods.

std::bind 像函数一样执行 function composition. The first argument must be a function object, i.e. something callable(例如,普通函数,或带有重载 operator() 的 class)。

调用 std::bind 复制其参数,"binds" 复制第一个参数(函数对象)的参数,returns 一个新的函数对象将调用函数对象的副本。

所以在一个简单的例子中:

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

这将值 1 绑定到函数 f,创建一个可以不带参数调用的新函数对象。当你调用 f1() 时,它会调用带有参数 1f,即它会调用 f(1) 和 return,无论 returns (在这种情况下只是 1).

return由 std::bind(f, 1) 编辑的东西的实际类型是某种 implementation-specific class 类型,可能称为 std::__detail::__bind_type<void(*)(int), int> 之类的东西。您不打算直接引用该类型,您可以使用 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} 的副本和由 [ 创建的 reference_wrapper<std::mt19937> 的副本=29=]。当您调用该新函数对象时,它将调用包含的分布,将其传递给 rng.

的引用

这意味着它可以不带参数调用,并且它将使用包含的随机引擎调用包含的随机数分布,并且 return 结果。

Q: std::function is helpfully referred to as 'a general-purpose polymorphic function wrapper' on cppreference.com. So is it a function that encapsulates a uint64_t return type?

A std::function<uint64_t()> 是一个函数对象的包装器,它可以不带参数调用并且 return 是一个 uint64_t(或者可以隐式转换为 uint64_t 的东西)。它可用于存储可复制构造的任意函数对象的副本,并且可以不带参数调用,并且 returns 可以转换为 uint64_t.

(更一般地说,std::function<R(A1, A2 ... AN)> 是一个函数对象的包装器,当使用 A1 类型的 N 个参数调用时 returns RA2 ... AN.)

由于您的 std::bind 调用的结果是一个可复制构造的函数对象,可以不带参数调用并且 return 是一个 uint64_t,您可以存储该 std::bindstd::function<uint64_t()> 中调用,当您调用 function 时,它将调用 bind 调用的结果,这将调用包含的分布,而 return结果。

Or again, making use of operator () syntax to drive the notion of a function?

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