Подтвердить что ты не робот

Зачем использовать std:: bind over lambdas в С++ 14?

До C++ 11 я часто использовал boost::bind или boost::lambda. Часть bind превратила ее в стандартную библиотеку (std::bind), другая часть стала частью основного языка (C++ lambdas) и значительно упростила использование lambdas. В настоящее время я почти не использую std::bind, так как с лямбдами C++ я могу делать практически все. Есть один допустимый вариант использования std::bind о котором я могу подумать:

struct foo
{
  template < typename A, typename B >
  void operator()(A a, B b)
  {
    cout << a << ' ' << b;
  }
};

auto f = bind(foo(), _1, _2);
f( "test", 1.2f ); // will print "test 1.2"

C++ 14 эквивалент для этого будет

auto f = []( auto a, auto b ){ cout << a << ' ' << b; }
f( "test", 1.2f ); // will print "test 1.2"

Гораздо короче и лаконичнее. (В C++ 11 это еще не работает из-за автоматических параметров.) Есть ли другой допустимый вариант использования std::bind превосходящий альтернативу C++ lambdas, или std::bind излишен с C++ 14?

4b9b3361

Ответ 1

Скотт Мейерс рассказал об этом . Это то, что я помню:

В С++ 14 ничего полезного bind не может сделать, что не может быть сделано с lambdas.

В С++ 11 однако есть некоторые вещи, которые нельзя сделать с lambdas:

  • Вы не можете перемещать переменные во время захвата при создании лямбда. Переменные всегда записываются как lvalues. Для привязки вы можете написать:

    auto f1 = std::bind(f, 42, _1, std::move(v));
    
  • Выражения не могут быть захвачены, только идентификаторы могут. Для привязки вы можете написать:

    auto f1 = std::bind(f, 42, _1, a + b);
    
  • Перегрузка аргументов для объектов функций. Это уже упоминалось в вопросе.

  • Невозможно совершенные аргументы

В С++ 14 все это возможно.

  • Пример перемещения:

    auto f1 = [v = std::move(v)](auto arg) { f(42, arg, std::move(v)); };
    
  • Пример выражения:

    auto f1 = [sum = a + b](auto arg) { f(42, arg, sum); };
    
  • См. вопрос

  • Идеальная пересылка: вы можете написать

    auto f1 = [=](auto&& arg) { f(42, std::forward<decltype(arg)>(arg)); };
    

Некоторые недостатки связывания:

  • Связывание связывается по имени, и в результате, если у вас есть несколько функций с тем же именем (перегруженные функции), bind не знает, какой из них использовать. Следующий пример не будет компилироваться, в то время как lambdas не будет иметь проблемы с ним:

    void f(int); void f(char); auto f1 = std::bind(f, _1, 42);
    
  • При использовании функций привязки менее склонны быть встроенными

С другой стороны, lambdas теоретически может генерировать больше кода шаблона, чем bind. Поскольку для каждой лямбда вы получаете уникальный тип. Для bind это происходит только тогда, когда у вас разные типы аргументов и другая функция (я предполагаю, что на практике, однако, это происходит не так часто, что вы связываете несколько раз с теми же аргументами и функциями).

То, что Джонатан Вакели упомянул в своем ответе, на самом деле является еще одной причиной не использовать привязку. Я не понимаю, почему вы хотели бы молча игнорировать аргументы.

Ответ 2

std::bind все еще может сделать одну вещь, которую не могут выполнять полиморфные lambdas: вызывать перегруженные функции

struct F {
  bool operator()(char, int);
  std::string operator()(char, char);
};

auto f = std::bind(F(), 'a', std::placeholders::_1);
bool b = f(1);
std::string s = f('b');

Обертка вызовов, созданная выражением связывания, вызывает разные функции в зависимости от аргументов, которые вы ей даете, закрытие из полиморфной лямбда С++ 14 может принимать разные типы аргументов, но не может принимать различное количество аргументов и всегда вызывает (специализации) ту же функцию на замыкании. Исправление: см. комментарии ниже

Обертка, возвращаемая std::bind, также может быть вызвана с большим количеством аргументов, и она будет игнорировать их, тогда как закрытие, созданное лямбдой, будет диагностировать попытки передать слишком много аргументов... но я не считаю, что преимущество std::bind:)

Ответ 3

Для меня допустимое использование для std::bind заключается в том, чтобы дать понять, что я использую функцию-член как предикат. То есть, если все, что я делаю, это вызов функции-члена, он связывается. Если я делаю дополнительный материал с аргументом (помимо вызова функции memeber), это лямбда:

using namespace std;
auto is_empty = bind(&string::empty, placeholders::_1); // bind = just map member
vector<string> strings;
auto first_empty = any_of(strings.begin(), strings.end(), is_empty);

auto print_non_empty = [](const string& s) {            // lambda = more than member
    if(s.empty())                // more than calling empty
        std::cout << "[EMPTY]";  // more than calling empty
    else                         // more than calling empty
        std::cout << s;          // more than calling empty
};
vector<string> strings;
for_each(strings.begin(), strings.end(), print_non_empty);

Ответ 4

Иногда это просто меньше кода. Учти это:

bool check(int arg1, int arg2, int arg3)
{
  return ....;
}

затем

wait(std::bind(check,a,b,c));

против лямбды

wait([&](){return check(a,b,c);});

Я думаю, что связывание легче читать здесь по сравнению с лямбда, которая выглядит как https://en.wikipedia.org/wiki/Brainfuck

Ответ 5

Другое отличие состоит в том, что аргументы привязки должны быть скопированы или перемещены, а лямбда может использовать переменные, захваченные ссылкой. См. Пример ниже:

#include <iostream>
#include <memory>

void p(const int& i) {
    std::cout << i << '\n';
}

int main()
{
    std::unique_ptr<int> f = std::make_unique<int>(3);

    // Direct
    p(*f);

    // Lambda ( ownership of f can stay in main )
    auto lp = [&f](){p(*f);};
    lp();

    // Bind ( does not compile - the arguments to bind are copied or moved)
    auto bp = std::bind(p, *f, std::placeholders::_1);
    bp();
}

Не уверен, что можно обойти проблему, чтобы использовать привязку выше, не меняя подписи void p(const int&).

Ответ 6

Просто расширив комментарий @BertR к этому ответу до чего-то тестируемого, хотя, признаюсь, я не смог найти решение, использующее std :: forward <> для работы.

#include <string>
#include <functional>
using namespace std::string_literals;

struct F {
    bool        operator()(char c, int  i) { return c == i;  };
    std::string operator()(char c, char d) { return ""s + d; };
};

void test() {
    { // using std::bind
        auto f = std::bind(F(), 'a', std::placeholders::_1);
        auto b = f(1);
        auto s = f('b');
    }
    { // using lambda with parameter pack
        auto x = [](auto... args) { return F()('a', args...); };
        auto b = x(1);
        auto s = x('b');
    }
}

Тест в компиляторе