C++函数对象-部分函数应用-绑定一或多个实参到函数对象(std::bind)

任何定义了函数调用操作符的对象都是函数对象。C++ 支持创建、操作新的函数对象,同时也提供了许多内置的函数对象。

部分函数应用

std::bind_front 与 std::bind 提供部分函数应用的支持,即绑定参数到函数以创建新函数。

绑定一或多个实参到函数对象

std::bind

template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );

(1) (C++11 起)

template< class R, class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );

(2) (C++11 起)

函数模板 bind 生成 f 的转发调用包装器。调用此包装器等价于以一些绑定到 args 的参数调用 f

参数

f - 可调用 (Callable) 对象(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针)
args - 要绑定的参数列表,未绑定参数为命名空间 std::placeholders 的占位符 _1, _2, _3... 所替换

返回值

未指定类型 T 的函数对象,满足 std::is_bind_expression<T>::value == true 。它有下列属性:

std::bind 返回类型

成员对象

std::bind 的返回类型保有从 std::forward<F>(f) 构造的 std::decay<F>::type 类型成员对象,和对于每个 args... 类型为 std::decay<Arg_i>::type 的各一个对象,类似地从 std::forward<Arg_i>(arg_i) 构造。

构造函数

std::bind 的返回类型的所有成员类型(说明如上)为可复制构造 (CopyConstructible) ,则它为可复制构造 (CopyConstructible) ,否则为可移动构造 (MoveConstructible) 。类型定义下列成员:

成员类型 result_type

1) (C++17 中弃用) 若 F 是指向函数或指向成员函数指针,则 result_typeF 的返回类型。若 F 是有嵌套 typedef result_type 的类类型,则 result_typeF::result_type 。否则不定义 result_type

2) (C++17 中弃用) result_type 准确地为 R

(C++20 前)

成员函数 operator()

给定从先前到 bind 调用获得的对象 g ,从函数调用表达式 g(u1, u2, ... uM) 调用它时,发生被存储对象的调用,如同以 std::invoke(fd, std::forward<V1>(v1), std::forward<V2>(v2), ..., std::forward<VN>(vN)) ,其中 fd 是 std::decay_t<F> 类型值,受绑定 v1, v2, ..., vN 参数的值和类型按以下方式确定。

  • 若存储的参数 arg 拥有类型 std::reference_wrapper<T> (例如,在起始的到 bind 调用中使用了 std::ref 或 std::cref ),则上述 std::invoke 调用中的 vnarg.get() 且同一调用中的类型 VnT& :存储的参数按引用传递进入被调用的函数对象。
  • 若存储的参数 arg 拥有类型 T 并满足 std::is_bind_expression<T>::value == true (例如,直接传递到起始的对 bind 调用的另一 bind 表达式),则 bind 进行函数组合:不是传递该 bind 子表达式将返回的函数对象,而是饥渴地调用该子表达式,并将其返回值传递给外层可调用对象。若 bind 子表达式拥有任何占位符参数,则将它们与外层 bind 共享(从 u1, u2, ... 中选出)。特别是,上述 std::invoke 调用中的参数 vnarg(std::forward<Uj>(uj)...) 而同一调用中的类型 Vn 是 std::result_of_t<T cv &(Uj&&...)>&& ( cv 限定与 g 的相同)。
  • 若存储的参数 arg 拥有类型 T 并满足 std::is_placeholder<T>::value != 0 (表示以如 std::placeholders::_1, _2, _3, ... 的占位符为到 bind 初始调用的参数),则将占位符所指示的参数( _1u1_2u2 等)传递给可调用对象:上述 std::invoke 调用中的参数 vn 是 std::forward<Uj>(uj) 而同一调用中对应类型 Vn 是 Uj&& 。
  • 否则,普通的存储参数 arg 作为左值参数传递给:上述 std::invoke 调用中的参数 vn 单纯地是 arg 且对应类型 VnT cv & ,其中 cv 是与 g 相同的 cv 限定。

若提供于到 g() 调用的一些参数不匹配存储于 g 的任何占位符,则求值并忽略未使用的参数。

g 为 volatile 限定(即其 cv 限定符是 volatile 或 const volatile ),则行为未定义。

异常

仅若从 std::forward<F>(f) 构造 std::decay<F>::type 抛出,或从 std::forward<Arg_i>(arg_i) 构造对应的任何 std::decay<Arg_i>::type 抛出才抛出异常,其中 Arg_iArgs... args 中第 i 个类型,而 arg_i 是第 i 个参数。

注意

可调用 (Callable) 中描述,调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针(可以包含智能指针,如 std::shared_ptr 与 std::unique_ptr),指向将访问其成员的对象。

到 bind 的参数被复制或移动,而且决不按引用传递,除非包装于 std::ref 或 std::cref 。

允许同一 bind 表达式中的多重占位符(例如多个 _1 ),但结果仅若对应参数( u1 )是左值或不可移动右值才良好定义。

调用示例

#include <random>
#include <iostream>
#include <memory>
#include <functional>

struct Cell
{
    int x;
    int y;

    Cell() = default;
    Cell(int a, int b): x(a), y(b) {}
    Cell(const Cell &cell)
    {
        x = cell.x;
        y = cell.y;
    }

    bool operator <(const Cell &cell) const
    {
        if (x == cell.x)
        {
            return y < cell.y;
        }
        else
        {
            return x < cell.x;
        }
    }

    Cell &operator+(const Cell &cell)
    {
        x += cell.x;
        y += cell.y;
        return *this;
    }

    void print_sum(Cell cell1, Cell cell2)
    {
        *this = cell1 + cell2;
        std::cout << "{" << x << "," << y << "}" << std::endl;
    }
};

std::ostream &operator<<(std::ostream &os, const Cell &cell)
{
    os << "{" << cell.x << "," << cell.y << "}";
    return os;
}

void Function1(Cell cell1, Cell cell2, Cell cell3, const Cell& cell4, Cell cell5)
{
    std::cout << cell1 << ' ' << cell2 << ' ' << cell3 << ' '
              << cell4 << ' ' << cell5 << std::endl;
}

Cell Function2(Cell cell)
{
    return cell;
}

struct Foo
{
    Cell data = {1024, 1024};
};

int main()
{
    using namespace std::placeholders;  // 对于 _1, _2, _3...

    // 演示参数重排序和按引用传递
    Cell cell1 = {101, 101};
    // ( _1 与 _2 来自 std::placeholders ,并表示将来会传递给 f1 的参数)
    auto function1 = std::bind(Function1, std::placeholders::_2,
                               Cell{102, 102}, std::placeholders::_1, std::cref(cell1), cell1);
    cell1 = {1024, 1024};
    // Cell{103, 103} 为 _1 所绑定, Cell{104, 104} 为 _2 所绑定,不使用 Cell{105, 105}
    // 进行到 Function1(Cell{104, 104}, Cell{102, 102}, Cell{103, 103}, cell1, Cell{101, 101}) 的调用
    function1(Cell{103, 103}, Cell{104, 104}, Cell{105, 105});

    // 嵌套 bind 子表达式共享占位符
    auto function2 = std::bind(Function1, std::placeholders::_3,
                               std::bind(Function2, std::placeholders::_3),
                               std::placeholders::_3, Cell{104, 104}, Cell{105, 105});
    // 进行到 Function1(Cell{108, 108}, Function2(Cell{108, 108}),
    //Cell{108, 108}, Cell{104, 104}, Cell{105, 105}) 的调用
    function2(Cell{106, 106}, Cell{107, 107}, Cell{108, 108});

    // 常见使用情况:以分布绑定 RNG
    std::default_random_engine e;
    std::uniform_int_distribution<> d(0, 10);
    std::function<int()> rnd = std::bind(d, e); // e 的一个副本存储于 rnd
    for (int n = 0; n < 10; ++n)
    {
        std::cout << rnd() << ' ';
    }
    std::cout << std::endl;

    // 绑定指向成员函数指针
    Cell functionCell = {101, 101};
    auto function3 = std::bind(&Cell::print_sum, &functionCell, Cell{106, 106}, Cell{107, 107});
    function3(Cell{108, 108});

    // 绑定指向数据成员指针
    Foo foo;
    auto function4 = std::bind(&Foo::data, std::placeholders::_1);
    std::cout << function4(foo) << std::endl;

    // 智能指针亦能用于调用被引用对象的成员
    std::cout << function4(std::make_shared<Foo>(foo)) << std::endl;

    return 0;
}

输出

{104,104} {102,102} {103,103} {1024,1024} {101,101}
{108,108} {108,108} {108,108} {104,104} {105,105}
0 1 8 5 5 2 0 7 7 10
{213,213}
{1024,1024}
{1024,1024}