有没有办法访问存储在 C++ 中 std::bind() 返回的函数对象中的参数?

Is there a way to access arguments stored in the function object returned by std::bind() in C++?

提问人:Armin Sobhani 提问时间:10/3/2023 最后编辑:Armin Sobhani 更新时间:10/5/2023 访问量:142

问:

我需要一种方法来分解函数模板中返回的函数对象及其参数。std::bind()

下面的代码片段显示了我想做什么:

#include <iostream>
#include <functional>

void foo(int x, double y, char z)
{
    std::cout << "x = " << x << ", y = " << y << ", z = " << z << '\n';
}

int main()
{
    auto f = std::bind(foo, 42, 3.14, 'a');
    std::cout << "The first argument is: " << std::get<0>(f) << '\n';
    std::cout << "The second argument is: " << std::get<1>(f) << '\n';
    std::cout << "The third argument is: " << std::get<2>(f) << '\n';
}

输出应为:

The first argument is: 42
The second argument is: 3.14
The third argument is: a

但它没有编译错误:

test.cpp:12:60: error: no matching function for call to ‘get<0>(std::_Bind<void (*(int, double, char))(int, double, char)>&)’

为什么我需要这个?

std::generate()并且可以接受引擎或分布来生成随机数。当您传递分配时,除了函子和 lambda 之外,还有一种标准方法:std::generate_n()std::bind

std::mt19937 R(666);
std::::uniform_int_distribution<int> D(0, 100);
std::vector<int> v(10);
std::generate(v.begin(), v.end(), std::bind(D, std::ref(R));

我开发了一种公平的并行算法(即,如果你使用相同的种子和分布,你总是得到相同的序列)。我希望它具有相同的接口,但该算法需要调用引擎的函数,这对于串行算法来说是不必要的。我的想法是用来查看是否传递了引擎或发行版。如果它是引擎,则调用,如果是绑定表达式,请先分解它,然后调用引擎。Lambda 在这里不起作用,因为算法无法访问引擎。 如果实现无法实现,那么我将实现我的 .generate()std::generate()discard()std::is_bind_expression<T>::valuediscard()discard()std::bindstd::bind

如果有更好的方法可以做到这一点,我会很高兴知道它。

C++ 11 C+ +-标准库 stdbind

评论

1赞 Ted Lyngmo 10/3/2023
有趣的问题,但你为什么需要这个?返回的对象并不意味着要去腌制,所以如果你真的需要它,感觉设计有问题。
0赞 Armin Sobhani 10/5/2023
@TedLyngmo 我在我的问题中添加了“为什么我需要这个?”部分。
0赞 Ted Lyngmo 10/5/2023
“既是引擎还是生成随机数的分布” - 不,它不接受分布。函子的签名应该是与发行版的签名不匹配的,发行版将生成器作为参数。“当你传递一个发行版时,标准的方式是 std::bind......” - 好吧,那么它就不再是一个发行版了。然后它只是一个普通的发电机。“to see if an engine or a distribution is passed” - 从不传递给 的发行版。为什么不为您的版本使用标准标签等?R()generatestd::execution::pargenerate

答:

10赞 YSC 10/3/2023 #1

这是不可能的。

std::bind 返回一个未指定的类型,仅保证以下内容:

  • 复制或移动构造函数;
  • (已弃用)类型别名;result_type
  • operator().

如果您需要问题中描述的功能,则必须提出自己的定义。bind

4赞 463035818_is_not_an_ai 10/3/2023 #2

std::bind可以被视为已弃用,并被 lambda 取代。我听说过仍然需要的案例,但我从未见过。std::bind

您可以编写自定义函数包装器,将参数存储在元组中。

#include <functional>
#include <tuple>
#include <iostream>

template <typename R,typename BoundTuple,typename ... Args>
struct my_function {
    BoundTuple bound;
    std::function<R(Args...)> func;
    template <typename...MoreArgs>
    R operator()(MoreArgs&& ... more) {
        return std::apply(func,std::tuple_cat(bound,std::tuple(more...)));
    }
};

template <typename R,typename ... Bound,typename ... Args>
my_function<R,std::tuple<Bound...>,Args...> my_bind(std::function<R(Args...)>&& f,Bound&&...b){
    return {{b...},f};
}

void foo(int x, double y, char z) {
    std::cout << "x = " << x << ", y = " << y << ", z = " << z << '\n';
}

int main()
{
    auto f = my_bind(std::function(foo), 42, 3.14, 'a');
    std::cout << "The first argument is: " << std::get<0>(f.bound) << '\n';
    std::cout << "The second argument is: " << std::get<1>(f.bound) << '\n';
    std::cout << "The third argument is: " << std::get<2>(f.bound) << '\n';
    f();
}

现场演示

但是,请对此持保留态度。不处理引用,只能绑定从左开始的参数。它只是为了说明这种方法。这种方法是否可以扩展到您的需求取决于细节。

以我的拙见,std::bind 的接口很糟糕(例如,“如果调用 g() 中提供的某些参数与存储在 g 中的任何占位符不匹配,则未使用的参数将被计算并丢弃。

评论

0赞 Jarod42 10/3/2023
那么如何处理place_holder呢?:-)
0赞 463035818_is_not_an_ai 10/3/2023
@Jarod42缺少更多内容(例如参考文献)。我在答案中添加了一些注释,以更清楚地表明代码只是一个玩具示例来说明该方法。
0赞 Jarod42 10/5/2023
@ArminSobhani:你有元组作为参数,只需处理......std::reference_wrapper
0赞 Jarod42 10/5/2023
顺便说一句,不是我的代码;-)
0赞 463035818_is_not_an_ai 10/5/2023
@ArminSobhani收到答案后编辑问题是有问题的,因为通常它会使已经发布的答案无效。当我说“引用未处理”时,我实际上是指调用者必须(通过使用)来处理它们,而在完整的实现中,我希望函数自动处理它们。另一方面,与 eg 所做的相比,有人认为允许调用者显式使用 ref 包装器。所以,同样,这真的取决于细节。std::reference_wrapperbindstd::thread