Поиск…


Синтаксис

  • [ default-capture , capture-list ] ( аргумент-список ) изменяемые атрибуты throw-specification -> return-type { lambda-body } // Порядок спецификаций и атрибутов лямбда.
  • [ capture-list ] ( список-аргумент ) { lambda-body } // Общее определение лямбда.
  • [=] ( argument-list ) { lambda-body } // Захватывает все необходимые локальные переменные по значению.
  • [&] ( argument-list ) { lambda-body } // Захватывает все необходимые локальные переменные по ссылке.
  • [ capture-list ] { lambda-body } // Список аргументов и спецификаторы могут быть опущены.

параметры

параметр подробности
по умолчанию захвата Указывает, как фиксируются все неперечисленные переменные. Может быть = (захват по значению) или & (захват по ссылке). Если они опущены, переменные, не включенные в список, недоступны в лямбда-теле . Захват по умолчанию должен предшествовать списку захвата .
Захват-лист Указывает, как локальные переменные становятся доступными в лямбда-теле . Переменные без префикса записываются по значению. Переменные с префиксом & записываются по ссылке. В методе класса this можно использовать, чтобы сделать все его члены доступными по ссылке. Неперечисленные переменные недоступны, если перед списком не предшествует захват по умолчанию .
аргумент-список Задает аргументы лямбда-функции.
изменчивый (необязательно) Обычно переменные, захваченные значением, являются const . Указание mutable делает их неконстантными. Изменения этих переменных сохраняются между вызовами.
вбрасывание спецификация (необязательно) Определяет поведение бросания исключения из лямбда-функции. Например: noexcept или throw(std::exception) .
атрибуты (необязательно) Любые атрибуты для лямбда-функции. Например, если лямбда-тело всегда выдает исключение, то можно использовать [[noreturn]] .
-> return-type (необязательно) Указывает тип возврата лямбда-функции. Требуется, когда тип возврата не может быть определен компилятором.
лямбда-тело Блок кода, содержащий реализацию лямбда-функции.

замечания

C ++ 17 (текущий проект) вводит constexpr , в основном constexpr , который можно оценить во время компиляции. Лямбда автоматически constexpr если она удовлетворяет требованиям constexpr , но вы также можете указать ее с помощью constexpr слова constexpr :

//Explicitly define this lambdas as constexpr
[]() constexpr {
    //Do stuff
}

Что такое лямбда-выражение?

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

Название «лямбда-выражение» происходит от лямбда-исчисления , которое является математическим формализмом, изобретенным в 1930-х годах церковью Алонсо для изучения вопросов о логике и вычислимости. Лэмбда-исчисление легло в основу LISP , функционального языка программирования. По сравнению с лямбда-исчислением и LISP, лямбда-выражения C ++ разделяют свойства неназванного и захватывают переменные из окружающего контекста, но им не хватает возможности работать и возвращать функции.

Выражение лямбда часто используется в качестве аргумента для функций, которые принимают вызываемый объект. Это может быть проще, чем создание именованной функции, которая будет использоваться только при передаче в качестве аргумента. В таких случаях лямбда-выражения обычно предпочтительнее, поскольку они позволяют определять функциональные объекты inline.

Лямбда состоит из трех частей: списка захвата [] , необязательного списка параметров () и тела {} , все из которых могут быть пустыми:

[](){}                // An empty lambda, which does and returns nothing

Список захвата

[] - список захвата . По умолчанию переменные охватывающей области недоступны лямбдой. Захват переменной делает ее доступной внутри лямбда, либо в виде копии, либо в качестве ссылки . Захваченные переменные становятся частью лямбда; в отличие от аргументов функции, они не должны передаваться при вызове лямбда.

int a = 0;                       // Define an integer variable
auto f = []()   { return a*9; }; // Error: 'a' cannot be accessed
auto f = [a]()  { return a*9; }; // OK, 'a' is "captured" by value
auto f = [&a]() { return a++; }; // OK, 'a' is "captured" by reference
                                 //      Note: It is the responsibility of the programmer
                                 //      to ensure that a is not destroyed before the
                                 //      lambda is called.
auto b = f();                    // Call the lambda function. a is taken from the capture list and not passed here.

Список параметров

() - список параметров , который почти такой же, как и в обычных функциях. Если лямбда не принимает аргументов, эти круглые скобки могут быть опущены (за исключением того, что вам нужно объявить lambda mutable ). Эти два лямбда эквивалентны:

auto call_foo  = [x](){ x.foo(); };
auto call_foo2 = [x]{ x.foo(); };
C ++ 14

Список параметров может использовать auto тип placeholder вместо фактических типов. Таким образом, этот аргумент ведет себя как шаблонный шаблон шаблона функции. Следующие lambdas эквивалентны, когда вы хотите отсортировать вектор в общем коде:

auto sort_cpp11 = [](std::vector<T>::const_reference lhs, std::vector<T>::const_reference rhs) { return lhs < rhs; }; 
auto sort_cpp14 = [](const auto &lhs, const auto &rhs) { return lhs < rhs; }; 

Тело функции

{} - это тело , такое же, как и в обычных функциях.


Вызов лямбда

Объектом результата лямбда-выражения является замыкание , которое можно вызвать с помощью operator() (как и с другими объектами функций):

int multiplier = 5;
auto timesFive = [multiplier](int a) { return a * multiplier; }; 
std::out << timesFive(2); // Prints 10

multiplier = 15;
std::out << timesFive(2); // Still prints 2*5 == 10

Тип возврата

По умолчанию выводится возвращаемый тип выражения лямбда.

[](){ return true; };

В этом случае тип возврата - bool .

Вы также можете вручную указать тип возврата, используя следующий синтаксис:

[]() -> bool { return true; };

Mutable Lambda

Объекты, захваченные значением в лямбда, по умолчанию неизменяемы. Это связано с тем, что operator() созданного объекта замыкания по умолчанию является const .

auto func = [c = 0](){++c; std::cout << c;};  // fails to compile because ++c
                                              // tries to mutate the state of
                                              // the lambda.

Модификация может быть разрешена с помощью ключевого слова mutable , которые делают operator() не const :

auto func = [c = 0]() mutable {++c; std::cout << c;};

Если используется вместе с возвращаемым типом, перед ним появляется mutable .

auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};

Пример, иллюстрирующий полезность лямбда

Перед C ++ 11:

C ++ 11
// Generic functor used for comparison
struct islessthan
{
    islessthan(int threshold) : _threshold(threshold) {}

    bool operator()(int value) const
    {
        return value < _threshold;
    }
private:
    int _threshold;
};

// Declare a vector
const int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> vec(arr, arr+5);

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));

Поскольку C ++ 11:

C ++ 11
// Declare a vector
std::vector<int> vec{ 1, 2, 3, 4, 5 };

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value < threshold; });

Указание типа возврата

Для lambdas с одним оператором return или несколькими операторами возврата, выражения которых имеют один и тот же тип, компилятор может выводить возвращаемый тип:

// Returns bool, because "value > 10" is a comparison which yields a Boolean result
auto l = [](int value) {
    return value > 10;
}

Для lambdas с несколькими операторами возврата разных типов компилятор не может выводить возвращаемый тип:

// error: return types must match if lambda has unspecified return type
auto l = [](int value) {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

В этом случае вы должны явно указать тип возвращаемого значения:

// The return type is specified explicitly as 'double'
auto l = [](int value) -> double {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

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

auto copy = [](X& x) { return x; };       // 'copy' returns an X, so copies its input
auto ref  = [](X& x) -> X& { return x; }; // 'ref' returns an X&, no copy

Захват по значению

Если вы укажете имя переменной в списке захвата, лямбда зафиксирует ее по значению. Это означает, что сгенерированный тип замыкания для лямбда хранит копию переменной. Это также требует, чтобы тип переменной был выполнен с возможностью копирования :

int a = 0;

[a]() {
    return a;   // Ok, 'a' is captured by value
};
C ++ 14
auto p = std::unique_ptr<T>(...);

[p]() {         // Compile error; `unique_ptr` is not copy-constructible
    return p->createWidget(); 
};

Начиная с C ++ 14, можно инициализировать переменные на месте. Это позволяет перемещать только типы, которые должны быть записаны в лямбда.

C ++ 14
auto p = std::make_unique<T>(...);

[p = std::move(p)]() {
    return p->createWidget(); 
};

Несмотря на то, что лямбда фиксирует переменные по значению, когда они даются по их имени, такие переменные не могут быть изменены внутри тела лямбда по умолчанию. Это связано с тем, что тип замыкания ставит лямбда-тело в объявление operator() const .

const относится к доступы к переменным - членам типа закрытия, и захватили переменные , которые являются членами закрытия (все выступления до наоборот):

int a = 0;

[a]() {
    a = 2;      // Illegal, 'a' is accessed via `const`

    decltype(a) a1 = 1; 
    a1 = 2; // valid: variable 'a1' is not const
};

Чтобы удалить const , вы должны указать ключевое слово mutable на лямбде:

int a = 0;

[a]() mutable {
    a = 2;      // OK, 'a' can be modified
    return a;
};

Поскольку a был захвачен по значению, любые изменения, сделанные при вызове лямбда, не повлияют на a . Значение было скопировано в лямбда , когда он был построен, поэтому копия лямбды о отделена от внешнего a a a переменному.

int a = 5 ; 
auto plus5Val = [a] (void) { return a + 5 ; } ; 
auto plus5Ref = [&a] (void) {return a + 5 ; } ; 

a = 7 ; 
std::cout << a << ", value " << plus5Val() << ", reference " << plus5Ref() ;
// The result will be "7, value 10, reference 12"

Обобщенный захват

C ++ 14

Lambdas может захватывать выражения, а не просто переменные. Это позволяет lambdas хранить типы только для перемещения:

auto p = std::make_unique<T>(...);

auto lamb = [p = std::move(p)]() //Overrides capture-by-value of `p`.
{
  p->SomeFunc();
};

Это перемещает внешнюю переменную p переменную захвата лямбда, также называемую p . lamb теперь владеет память , выделенную make_unique . Поскольку закрытие содержит тип, который не копируется, это означает, что сам lamb сам не копируется. Но его можно переместить:

auto lamb_copy = lamb; //Illegal
auto lamb_move = std::move(lamb); //legal.

Теперь lamb_move владеет памятью.


Обратите внимание, что std::function<> требует, чтобы сохраненные значения были скопируемыми. Вы можете написать свою собственную std::function only, требующую перемещения только , или вы можете просто набить лямбду в обертку shared_ptr :

auto shared_lambda = [](auto&& f){
  return [spf = std::make_shared<std::decay_t<decltype(f)>>(decltype(f)(f))]
  (auto&&...args)->decltype(auto) {
    return (*spf)(decltype(args)(args)...);
  };
};
auto lamb_shared = shared_lambda(std::move(lamb_move));

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


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

int a = 0;

auto lamb = [&v = a](int add) //Note that `a` and `v` have different names
{
  v += add; //Modifies `a`
};

lamb(20); //`a` becomes 20.

Обобщение захвата вообще не требует захвата внешней переменной. Он может фиксировать произвольное выражение:

auto lamb = [p = std::make_unique<T>(...)]()
{
    p->SomeFunc();
}

Это полезно для предоставления произвольных значений lambdas, которые они могут удерживать и потенциально изменять, без необходимости объявлять их извне в лямбда. Конечно, это полезно только в том случае, если вы не намереваетесь получить доступ к этим переменным после того, как лямбда завершила свою работу.

Захват по ссылке

Если вы предшествуете имени локальной переменной с помощью & , тогда переменная будет записана по ссылке. Концептуально это означает, что тип закрытия лямбда будет иметь ссылочную переменную, инициализированную как ссылку на соответствующую переменную из-за пределов лямбда. Любое использование переменной в лямбда-теле будет относиться к исходной переменной:

// Declare variable 'a'
int a = 0;

// Declare a lambda which captures 'a' by reference
auto set = [&a]() {
    a = 1;
};

set();
assert(a == 1);

Ключевое слово mutable не требуется, потому a сам не является const .

Конечно, захват по ссылке означает, что лямбда не должна выходить за пределы диапазона переменных, которые она захватывает. Таким образом, вы можете вызывать функции, которые выполняют функцию, но вы не должны вызывать функцию, которая будет хранить лямбда вне сферы ваших ссылок. И вы не должны возвращать лямбду.

Удержание по умолчанию

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

int a = 1;
int b = 2;

// Default capture by value
[=]() { return a + b; }; // OK; a and b are captured by value

// Default capture by reference
[&]() { return a + b; }; // OK; a and b are captured by reference

Явный захват по-прежнему может выполняться наряду с неявным захватом по умолчанию. Явное определение захвата переопределит захват по умолчанию:

int a = 0;
int b = 1;

[=, &b]() {
    a = 2; // Illegal; 'a' is capture by value, and lambda is not 'mutable'
    b = 2; // OK; 'b' is captured by reference
};

Родовые лямбды

C ++ 14

Лямбда-функции могут принимать аргументы произвольных типов. Это позволяет лямбда быть более общей:

auto twice = [](auto x){ return x+x; };

int i = twice(2); // i == 4
std::string s = twice("hello"); // s == "hellohello"

Это реализовано в C ++, заставив operator() типа закрытия operator() перегрузить функцию шаблона. Следующий тип имеет эквивалентное поведение для вышеуказанного лямбда-замыкания:

struct _unique_lambda_type
{
  template<typename T>
  auto operator() (T x) const {return x + x;}
};

Не все параметры в общей лямбде должны быть общими:

[](auto x, int y) {return x + y;}

Здесь x выводится на основе первого аргумента функции, а y всегда будет int .

Родовые lambdas также могут принимать аргументы по ссылке, используя обычные правила для auto и & . Если общий параметр принимается как auto&& , это ссылка на пересылку переданного аргумента, а не ссылку rvalue :

auto lamb1 = [](int &&x) {return x + 5;};
auto lamb2 = [](auto &&x) {return x + 5;};
int x = 10;
lamb1(x); // Illegal; must use `std::move(x)` for `int&&` parameters.
lamb2(x); // Legal; the type of `x` is deduced as `int&`.

Лямбда-функции могут быть вариативными и идеально продвигать свои аргументы:

auto lam = [](auto&&... args){return f(std::forward<decltype(args)>(args)...);};

или же:

auto lam = [](auto&&... args){return f(decltype(args)(args)...);};

который работает только «правильно» с переменными типа auto&& .

Сильная причина использовать общие лямбды - это синтаксис посещения.

boost::variant<int, double> value;
apply_visitor(value, [&](auto&& e){
  std::cout << e;
});

Здесь мы находимся полиморфно; но в других контекстах имена типа, который мы проходим, не интересны:

mutex_wrapped<std::ostream&> os = std::cout;
os.write([&](auto&& os){
  os << "hello world\n";
});

Повторение типа std::ostream& is noise здесь; это будет означать необходимость упоминать тип переменной каждый раз, когда вы ее используете. Здесь мы создаем посетителя, но не полиморфного; auto используется по той же причине, что вы можете использовать auto в цикле for(:) .

Преобразование в указатель функции

Если список захвата лямбда пуст, то лямбда имеет неявное преобразование в указатель функции, который принимает те же аргументы и возвращает тот же тип возврата:

auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};

using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // implicit conversion

Такое преобразование также может быть выполнено с использованием оператора унарного пользователя:

func_ptr sorter_func2 = +sorter; // enforce implicit conversion

Вызов этого указателя функции ведет себя точно так же, как вызов operator() на лямбда. Указатель этой функции никоим образом не зависит от существования источника лямбда-замыкания. Поэтому он может пережить лямбда-закрытие.

Эта функция в основном полезна для использования lambdas с API, которые имеют дело с указателями функций, а не с объектами функций C ++.

C ++ 14

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

auto sorter = [](auto lhs, auto rhs) { return lhs < rhs; };
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter;  // deduces int, int
// note however that the following is ambiguous
// func_ptr sorter_func2 = +sorter;

Класс лямбда и захват этого

Выражение lambda, оцениваемое в функции-члене класса, неявно является другом этого класса:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    // definition of a member function
    void Test()
    {
        auto lamb = [](Foo &foo, int val)
        {
            // modification of a private member variable
            foo.i = val;
        };
        
        // lamb is allowed to access a private member, because it is a friend of Foo
        lamb(*this, 30);
    }
};

Такая лямбда не только друг этого класса, но и тот же доступ, что и класс, объявленный внутри.

Lambdas может захватить this указатель, который представляет экземпляр объекта, на который была вызвана внешняя функция. Это делается путем добавления this в список захвата:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture the this pointer by value
        auto lamb = [this](int val)
        {
            i = val;
        };
        
        lamb(30);
    }
};

Когда this будет зафиксировано, лямбда может использовать имена членов своего класса, как если бы они находились в его содержащем классе. Поэтому для таких членов применяется неявное this-> .

Имейте в виду, что this фиксируется значением, но не значением типа. Он захватывается значением this , которое является указателем . Таким образом, лямбда не владеет this . Если лямбда вне жизни жизни объекта, который ее создал, лямбда может стать недействительной.

Это также означает, что лямбда может изменить this не будучи объявленным mutable . Это указатель, который является const , а не объектом, на который указывают. То есть, если внешняя функция-член сама не является функцией const .

Кроме того, имейте в виду, что положения захвата по умолчанию, как [=] и [&] , также будут фиксировать this неявно. И они оба фиксируют его по значению указателя. В самом деле, это ошибка, указывающая this в списке захвата при задании по умолчанию.

C ++ 17

Lambdas может захватить копию this объекта, созданного в момент создания лямбда. Это делается путем добавления *this в список захвата:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture a copy of the object given by the this pointer
        auto lamb = [*this](int val) mutable
        {
            i = val;
        };
        
        lamb(30); // does not change this->i
    }
};

Перенос лямбда-функций на C ++ 03 с использованием функторов

Лямбда-функции в C ++ - это синтаксический сахар, который обеспечивает очень сжатый синтаксис для написания функторов . Таким образом, эквивалентная функциональность может быть получена в C ++ 03 (хотя и намного более подробно) путем преобразования лямбда-функции в функтор:

// Some dummy types:
struct T1 {int dummy;};
struct T2 {int dummy;};
struct R {int dummy;};

// Code using a lambda function (requires C++11)
R use_lambda(T1 val, T2 ref) {
  // Use auto because the type of the lambda is unknown.
  auto lambda = [val, &ref](int arg1, int arg2) -> R {
    /* lambda-body */
    return R();
  };
  return lambda(12, 27);
}

// The functor class (valid C++03)
// Similar to what the compiler generates for the lambda function.
class Functor {
  // Capture list.
  T1 val;
  T2& ref;

public:
  // Constructor
  inline Functor(T1 val, T2& ref) : val(val), ref(ref) {}

  // Functor body
  R operator()(int arg1, int arg2) const {
    /* lambda-body */
    return R();
  }
};

// Equivalent to use_lambda, but uses a functor (valid C++03).
R use_functor(T1 val, T2 ref) {
  Functor functor(val, ref);
  return functor(12, 27);
}

// Make this a self-contained example.
int main() {
  T1 t1;
  T2 t2;
  use_functor(t1,t2);
  use_lambda(t1,t2);
  return 0;
}

Если лямбда-функция mutable то сделайте оператор-оператор функтора неконстантным, то есть:

R operator()(int arg1, int arg2) /*non-const*/ {
  /* lambda-body */
  return R();
}

Рекурсивные лямбды

Предположим, мы хотим написать gcd() Евклида как лямбда. Как функция, это:

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a%b);
}

Но лямбда не может быть рекурсивной, она не имеет возможности ссылаться на себя. Лямбда не имеет имени и использование this в теле лямбды относится к захваченному this (предполагая , что лямбда создаются в теле функции члена, в противном случае это ошибка). Итак, как мы решаем эту проблему?

Использовать std::function

У нас может быть захват лямбда ссылкой на еще не сконструированную std::function

std::function<int(int, int)> gcd = [&](int a, int b){
    return b == 0 ? a : gcd(b, a%b);
};

Это работает, но следует использовать экономно. Он медленный (теперь мы используем стирание стилей вместо прямого вызова функции), он хрупкий (копирование gcd или возвращение gcd будет ломаться, поскольку лямбда относится к исходному объекту), и он не будет работать с общими лямбдами.

Используя два интеллектуальных указателя:

auto gcd_self = std::make_shared<std::unique_ptr< std::function<int(int, int)> >>();
*gcd_self = std::make_unique<std::function<int(int, int)>>(
  [gcd_self](int a, int b){
    return b == 0 ? a : (**gcd_self)(b, a%b);
  };
};

Это добавляет много косвенности (что является накладными расходами), но может быть скопировано / возвращено, и все копии разделяют состояние. Это позволяет вам вернуть лямбду и в противном случае менее хрупким, чем вышеупомянутое решение.

Используйте Y-комбинатор

С помощью короткой служебной структуры мы можем решить все эти проблемы:

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // the lambda should take the first argument as `auto&& recurse` or similar.
        return f(*this, std::forward<Args>(args)...);
    }
};
// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}
// (Be aware that in C++17 we can do better than a `make_` function)

мы можем реализовать наш gcd как:

auto gcd = make_y_combinator(
  [](auto&& gcd, int a, int b){
    return b == 0 ? a : gcd(b, a%b);
  }
);

y_combinator - это концепция из лямбда-исчисления, которая позволяет вам иметь рекурсию, не имея возможности назвать себя до тех пор, пока вы не определитесь. Это именно то, что есть у лямбда.

Вы создаете лямбду, которая принимает «recurse» в качестве своего первого аргумента. Когда вы хотите рекурсивно, вы передаете аргументы для рекурсии.

Затем y_combinator возвращает объект функции, который вызывает эту функцию со своими аргументами, но с подходящим объектом « y_combinator » (а именно сам y_combinator ) в качестве своего первого аргумента. Он перенаправляет остальные аргументы, которые вы называете y_combinator на лямбда.

Короче:

auto foo = make_y_combinator( [&](auto&& recurse, some arguments) {
  // write body that processes some arguments
  // when you want to recurse, call recurse(some other arguments)
});

и у вас есть рекурсия в лямбда без каких-либо серьезных ограничений или значительных накладных расходов.

Использование lambdas для распаковки пакетов встроенных параметров

C ++ 14

Для распаковки пакета параметров традиционно требуется написать вспомогательную функцию для каждого раза, когда вы хотите это сделать.

В этом примере игрушек:

template<std::size_t...Is>
void print_indexes( std::index_sequence<Is...> ) {
  using discard=int[];
  (void)discard{0,((void)(
    std::cout << Is << '\n' // here Is is a compile-time constant.
  ),0)...};
}
template<std::size_t I>
void print_indexes_upto() {
  return print_indexes( std::make_index_sequence<I>{} );
}

print_indexes_upto хочет создать и распаковать пакет параметров индексов. Для этого он должен вызвать вспомогательную функцию. Каждый раз, когда вы хотите распаковать созданный вами пакет параметров, вам нужно создать для него настраиваемую вспомогательную функцию.

Этого можно избежать с помощью лямбда.

Вы можете распаковать пакеты параметров в набор вызовов лямбда, например:

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    using discard=int[];
    (void)discard{0,(void(
      f( index<Is> )
    ),0)...};
  };
}

template<std::size_t N>
auto index_over(index_t<N> = {}) {
  return index_over( std::make_index_sequence<N>{} );
}
C ++ 17

С помощью выражений fold, index_over() можно упростить до:

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    ((void)(f(index<Is>)), ...);
  };
}

Как только вы это сделаете, вы можете использовать это, чтобы заменить необходимость вручную распаковывать пакеты параметров со второй перегрузкой в ​​другом коде, позволяя распаковать пакеты параметров «inline»:

template<class Tup, class F>
void for_each_tuple_element(Tup&& tup, F&& f) {
  using T = std::remove_reference_t<Tup>;
  using std::tuple_size;
  auto from_zero_to_N = index_over< tuple_size<T>{} >();

  from_zero_to_N(
    [&](auto i){
      using std::get;
      f( get<i>( std::forward<Tup>(tup) ) );
    }
  );
}

auto i переданный лямбда index_over является std::integral_constant<std::size_t, ???> index_over std::integral_constant<std::size_t, ???> . Это преобразование constexpr в std::size_t которое не зависит от состояния this , поэтому мы можем использовать его как константу времени компиляции, например, когда мы передаем его в std::get<i> выше.

Чтобы вернуться к примеру игрушки в верхней части, перепишите его как:

template<std::size_t I>
void print_indexes_upto() {
  index_over(index<I>)([](auto i){
    std::cout << i << '\n'; // here i is a compile-time constant
  });
}

который намного короче и сохраняет логику в коде, который ее использует.

Живой пример игры.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow