Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Lambda expressions in details

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 18. Boost.Lambda

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Lambda expressions in details

В этом разделе подробно описаны различные категории выражений лямбда. Мы посвящаем отдельный раздел каждой из возможных форм выражения лямбда.

Placeholders

BLL определяет три типа заполнителей:<placeholder1_type>,<placeholder2_type>и<placeholder3_type>. BLL имеет предопределенную переменную заполнителя для каждого типа заполнителя:<_1>,<_2>и<_3>. Однако пользователь не обязан использовать эти заполнители. Легко определить держателей с альтернативными именами. Это делается путем определения новых переменных типов заполнителей. Например:

boost::lambda::placeholder1_type X;
boost::lambda::placeholder2_type Y;
boost::lambda::placeholder3_type Z;

С этими переменными<X += Y * Z>эквивалентно<_1 += _2 * _3>.

Использование заполнителей в лямбда-выражении определяет, является ли полученная функция нулярной, унарной, бинарной или 3-аричной. Самый высокий индекс плаценты является решающим. Например:

_1 + 5              // unary
_1 * _1 + _1        // unary
_1 + _2             // binary
bind(f, _1, _2, _3) // 3-ary
_3 + 10             // 3-ary

Заметим, что последняя строка создает 3-арную функцию, которая добавляет<10>к еетретийаргумент. Первые два аргумента отброшены. Кроме того, лямбда-функторы имеют только минимальное удобство. Всегда можно привести больше аргументов (по количеству поддерживаемых заполнителей), которые действительно необходимы. Остальные аргументы просто отбрасываются. Например:

int i, j, k; 
_1(i, j, k)        // returns i, discards j and k
(_2 + _2)(i, j, k) // returns j+j, discards i and k

См.раздел под названием “ Lambda functor arity ”для обоснования дизайна этой функциональности.

В дополнение к этим трем типам заполнителей существует также четвертый тип заполнителей<placeholderE_type>. Использование этого заполнителя определяется вразделе под названием & #8220; Исключения & #8221;, описывающем обработку исключений в выражениях лямбда.

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

int i = 1; 
(_1 += 2)(i);         // i is now 3
(++_1, cout << _1)(i) // i is now 4, outputs 4

Operator expressions

Основное правило заключается в том, что любое обращение оператора C++ с хотя бы одним аргументом, являющимся лямбда-выражением, само по себе является лямбда-выражением. Поддерживаются практически все перегруженные операторы. Например, следующее является действительным выражением лямбда:

cout << _1, _2[_3] = _1 && false

Однако существуют некоторые ограничения, которые исходят из правил перегрузки оператора C++, и некоторые особые случаи.

Operators that cannot be overloaded

Некоторые операторы вообще не могут быть перегружены<::>,<.>,<.*>. Для некоторых операторов требования к типам возврата не позволяют перегружать их для создания лямбда-функторов. Эти операторы<->.>,<->>,<new>,<new[]>,<delete>,<delete[]>и<?:>(условный оператор).

Assignment and subscript operators

Эти операторы должны быть реализованы как члены класса. Следовательно, левый операнд должен быть выражением лямбда. Например:

int i; 
_1 = i;      // ok
i = _1;      // not ok. i is not a lambda expression

Существует простое решение этого ограничения, описанное вразделе под названием “ Задержка констант и переменных”. Короче говоря, аргумент левой руки можно явно превратить в лямбда-функтор, обернув его специальной функцией<var>:

var(i) = _1; // ok

Logical operators

Логические операторы подчиняются правилам оценки короткого замыкания. Например, в следующем коде<i>никогда не увеличивается:

bool flag = true; int i = 0;
(_1 || ++_2)(flag, i);

Comma operator

Оператором запятой является& #8220;сепаратор заявлений& #8221;в выражениях лямбда. Поскольку запятая также является разделителем между аргументами в вызове функции, иногда необходимы дополнительные скобки:

for_each(a.begin(), a.end(), (++_1, cout << _1));

Без дополнительной скобки<++_1, cout << _1>код будет интерпретироваться как попытка вызвать<for_each>с четырьмя аргументами.

Созданный оператором запятой лямбда-функтор придерживается правила C++ всегда оценивать левый операнд перед правым. В приведенном выше примере каждый элемент<a>сначала приращен, затем записан в поток.

Function call operator

Операторы вызова функций имеют эффект оценки лямбда-функтора. Звонки с небольшим количеством аргументов приводят к ошибке компиляции времени.

Member pointer operator

Оператор указателя<operator->*>может быть перегружен свободно. Следовательно, для определенных пользователем типов оператор указателя участника не является особым случаем. Встроенный смысл, однако, является несколько более сложным случаем. Встроенный оператор указателя члена применяется, если левый аргумент является указателем на объект некоторого класса<A>, а аргумент правой руки является указателем на член<A>или указателем на член класса, из которого<A>происходит. Мы должны разделить два случая:

  • Аргумент правой руки является указателем на элемент данных. В этом случае лямбда-функтор просто выполняет замену аргумента и вызывает встроенного оператора указателя члена, который возвращает ссылку на указанный член. Например:

    struct A { int d; };
    A* a = new A();
      ...
    (a ->* &A::d);     // returns a reference to a->d 
    (_1 ->* &A::d)(a); // likewise
    

  • Аргумент правой руки указывает на функцию члена. Для такого встроенного вызова результат является своего рода отложенным вызовом функции участника. За таким выражением должен следовать список аргументов функции, с которым выполняется вызов функции запаздывающего члена. Например:

    struct B { int foo(int); };
    B* b = new B();
      ...
    (b ->* &B::foo)         // returns a delayed call to b->foo
                            // a function argument list must follow
    (b ->* &B::foo)(1)      // ok, calls b->foo(1)
    (_1 ->* &B::foo)(b);    // returns a delayed call to b->foo, 
                            // no effect as such
    (_1 ->* &B::foo)(b)(1); // calls b->foo(1)
    

Bind expressions

Связанные выражения могут иметь две формы:

bind(target-function, bind-argument-list)
bind(target-member-function, object-argument, bind-argument-list)

Выражение связывания задерживает вызов функции. Если этоцелевая функцияn-ary, то<bind-argument-list>должен содержатьnаргументы. В текущей версии BLL должно быть 0<= n<=9. Для членских функций число аргументов должно быть не более 8, так как объектный аргумент занимает одну аргументную позицию. По сути,<bind-argument-list>должен быть обоснованным списком аргументов для целевой функции, за исключением того, что любой аргумент может быть заменен заполнителем или, в более общем смысле, лямбда-выражением. Обратите внимание, что целевой функцией может быть лямбда-выражение. Результатом выражения связывания является либо нулевой, унарный, бинарный или 3-аричный функциональный объект в зависимости от использования заполнителей в<bind-argument-list>(см.раздел под названием & #8220; Заполнители & #8221;).

Тип возврата лямбда-функтора, созданный выражением связывания, может быть задан как явно заданный параметр шаблона, как в следующем примере:

bind<RET>(target-function, bind-argument-list)

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

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

Function pointers or references as targets

Целевая функция может быть указателем или ссылкой на функцию, и она может быть как связанной, так и несвязанной. Например:

X foo(A, B, C); A a; B b; C c;
bind(foo, _1, _2, c)(a, b);
bind(&foo, _1, _2, c)(a, b);
bind(_1, a, b, c)(foo);

Вычет типа возврата всегда преуспевает с этим типом выражений связывания.

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

void foo(int);
void foo(float);
int i; 
  ...
bind(&foo, _1)(i);                            // error 
  ...
void (*pf1)(int) = &foo;
bind(pf1, _1)(i);                             // ok
bind(static_cast<void(*)(int)>(&foo), _1)(i); // ok

Member functions as targets

Синтаксис для использования указателей на функцию члена в выражении связывания:

bind(target-member-function, object-argument, bind-argument-list)

Аргумент объекта может быть ссылкой или указателем на объект, BLL поддерживает оба случая с однородным интерфейсом:

bool A::foo(int) const; 
A a;
vector<int> ints; 
  ...
find_if(ints.begin(), ints.end(), bind(&A::foo, a, _1)); 
find_if(ints.begin(), ints.end(), bind(&A::foo, &a, _1));

Аналогично, если объектный аргумент несвязан, полученный лямбда-функтор можно назвать как указателем, так и ссылкой:

bool A::foo(int); 
list<A> refs; 
list<A*> pointers; 
  ...
find_if(refs.begin(), refs.end(), bind(&A::foo, _1, 1)); 
find_if(pointers.begin(), pointers.end(), bind(&A::foo, _1, 1));

Несмотря на то, что интерфейсы одинаковы, существуют важные семантические различия между использованием указателя или ссылки в качестве аргумента объекта. Различия проистекают из того, как<bind>функции принимают свои параметры и как связанные параметры хранятся в лямбда-функтора. Аргумент объекта имеет тот же механизм пропускания и хранения параметра, что и любой другой слот аргумента связывания (см.раздел под названием “ Хранение связанных аргументов в функциях лямбды”); он передается как ссылка const и хранится как копия const в функторе лямбды. Это создает некоторую асимметрию между лямбда-функтором и оригинальной функцией члена, и между, казалось бы, похожими лямбда-функторами. Например:

class A {
  int i; mutable int j;
public:
  A(int ii, int jj) : i(ii), j(jj) {};
  void set_i(int x) { i = x; }; 
  void set_j(int x) const { j = x; }; 
};

Когда используется указатель, поведение - это то, что может ожидать программист:

A a(0,0); int k = 1;
bind(&A::set_i, &a, _1)(k); // a.i == 1
bind(&A::set_j, &a, _1)(k); // a.j == 1

Несмотря на то, что сохраняется конст-копия аргумента объекта, исходный объект<a>все еще изменяется. Это происходит потому, что объектный аргумент является указателем, а указатель копируется, а не объектом, на который он указывает. Когда мы используем ссылку, поведение отличается:

A a(0,0); int k = 1;
bind(&A::set_i, a, _1)(k); // error; a const copy of a is stored. 
                           // Cannot call a non-const function set_i
bind(&A::set_j, a, _1)(k); // a.j == 0, as a copy of a is modified

Чтобы предотвратить копирование, можно использовать<ref>или<cref>обертки<var>и<constant_ref>:

bind(&A::set_i, ref(a), _1)(k); // a.j == 1
bind(&A::set_j, cref(a), _1)(k); // a.j == 1

Обратите внимание, что предыдущая дискуссия имеет отношение только к связанным аргументам. Если аргумент объекта несвязан, режим прохождения параметра всегда основан на ссылке. Следовательно, аргумент<a>не копируется в призывах к двум лямбда-функторам ниже:

A a(0,0);
bind(&A::set_i, _1, 1)(a); // a.i == 1
bind(&A::set_j, _1, 1)(a); // a.j == 1

Member variables as targets

Указатель на переменную члена на самом деле не является функцией, но первый аргумент на функцию<bind>, тем не менее, может быть указателем на переменную члена. Ссылка на такое выражение связывания возвращает ссылку на элемент данных. Например:

struct A { int data; };
A a;
bind(&A::data, _1)(a) = 1;     // a.data == 1

Уважаются квалификаторы cv объекта, к члену которого осуществляется доступ. Например, следующее пытается записать в конст-место:

const A ca = a;
bind(&A::data, _1)(ca) = 1;     // error

Function objects as targets

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

The result_type typedef

BLL поддерживает стандартную библиотечную конвенцию декларирования типа возврата функционального объекта с идентификатором типа члена<result_type>в классе функционального объекта. Вот простой пример:

struct A {
  typedef B result_type;
  B operator()(X, Y, Z); 
};

Если объект функции не определяет типдеф<result_type>, способ, описанный ниже<sig>шаблон, пытается разрешить тип возврата объекта функции. Если объект функции определяет как<result_type>, так и<sig>,<result_type>имеет приоритет.

The sig template

Другим механизмом, позволяющим BLL знать тип(ы) возврата объекта функции, является определение структуры шаблона члена<sig<Args>>с типдефом<type>, который определяет тип возврата. Вот простой пример:

struct A {
  template <class Args> struct sig { typedef B type; }
  B operator()(X, Y, Z); 
};

Аргумент шаблона<Args>представляет собой<tuple>(точнее список<cons>) типкортеж], где первым элементом является сам тип объекта функции, а остальными элементами являются типы аргументов, с помощью которых объект функции называется. Это может показаться слишком сложным по сравнению с определением типа<result_type>. Говер, есть два существенных ограничения с использованием простого шрифта для выражения типа возврата:

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

  2. Если оператор вызова функции является шаблоном, тип результата может зависеть от параметров шаблона. Следовательно, typedef также должен быть шаблоном, который язык C++ не поддерживает.

Следующий код показывает пример, где тип возврата зависит от типа одного из аргументов и как эта зависимость может быть выражена шаблоном<sig>:

struct A {
  // the return type equals the third argument type:
  template<class T1, class T2, class T3>
  T3 operator()(const T1& t1, const T2& t2, const T3& t3) const;
  template <class Args> 
  class sig {
    // get the third argument type (4th element)
    typedef typename 
      boost::tuples::element<3, Args>::type T3;
  public:
    typedef typename 
      boost::remove_cv<T3>::type type;
  };
};

Элементы кортежа<Args>всегда являются нереферентными типами. Кроме того, типы элементов могут иметь конст или летучий квалификатор (совместно именуемыйcv-квалификаторы), или оба. Это связано с тем, что квалификаторы в аргументах могут влиять на тип возврата. Причина включения потенциально cv-квалифицированного типа объекта функции в набор<Args>заключается в том, что класс объекта функции может содержать как конст, так и неконст (или летучие, даже конст-летучие) операторы вызова функции, и каждый из них может иметь различный тип возврата.

Шаблон<sig>можно рассматривать какметафункцию, которая отображает набор типа аргумента на тип результата вызова, выполненного с аргументами типов в наборе. Как показывает пример выше, шаблон может оказаться несколько сложным. Типичными задачами, которые необходимо выполнить, являются извлечение соответствующих типов из кортежа, удаление cv-квалификаторов и т.д. См. библиотеки Boost type_traits[type_traits]и Tuple[type_traits]для инструментов, которые могут помочь в этих задачах. Шаблоны<sig>являются усовершенствованной версией аналогичного механизма, впервые представленного в библиотеке FC++[fc++].

Overriding the deduced return type

Система вычета типа возврата может быть не в состоянии вывести типы возврата некоторых операторов, определенных пользователем, или связать выражения с объектами класса. Предусмотрен специальный лямбда-тип выражения, в котором четко указывается тип возврата и преобладает система вычета. Чтобы указать, что тип возврата лямбда-функтора, определяемый выражением лямбда<e>, равен<T>, можно написать:

ret<T>(e);

Эффект заключается в том, что вычет типа возврата вообще не выполняется для выражения лямбда<e>, а вместо этого<T>используется в качестве типа возврата. Очевидно, что<T>не может быть произвольным типом, истинный результат лямбда-функтора должен быть неявно конвертируемым в<T>. Например:

A a; B b;
C operator+(A, B);
int operator*(A, B); 
  ...
ret<D>(_1 + _2)(a, b);     // error (C cannot be converted to D)
ret<C>(_1 + _2)(a, b);     // ok
ret<float>(_1 * _2)(a, b); // ok (int can be converted to float)
  ...
struct X {
  Y operator(int)();   
};
  ...
X x; int i;
bind(x, _1)(i);            // error, return type cannot be deduced
ret<Y>(bind(x, _1))(i);    // ok

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

bind<Z>(x, _1)(i);

Эта функция смоделирована после библиотеки Boost Bind[связывает].

Обратите внимание, что внутри вложенных лямбда-выражений<ret>должно использоваться в каждом подвыражении, где вычет в противном случае не будет выполнен. Например:

A a; B b;
C operator+(A, B); D operator-(C);
  ...
ret<D>( - (_1 + _2))(a, b); // error 
ret<D>( - ret<C>(_1 + _2))(a, b); // ok

Если вы обнаружите, что используете<ret>многократно с одними и теми же типами, стоит при продлении вычета типа возврата (см.раздел под названием & #8220; Расширение системы вычета типа возврата & #8221;).

Nullary lambda functors and ret

Как указано выше, эффект<ret>заключается в том, чтобы предотвратить вычет типа возврата. Однако есть исключение. Из-за того, как работает инстанциация шаблона C++, компилятор всегда вынужден инстанцировать шаблоны дедукции возвратного типа для лямбда-функторов с нулевым аргументом. Это создает небольшую проблему с<ret>, лучше всего описанную в примере:

struct F { int operator()(int i) const; }; 
F f;
  ...
bind(f, _1);           // fails, cannot deduce the return type
ret<int>(bind(f, _1)); // ok
  ...
bind(f, 1);            // fails, cannot deduce the return type
ret<int>(bind(f, 1));  // fails as well!

BLL не может вывести типы возврата вышеперечисленных вызовов связывания, поскольку<F>не определяет типдеф<result_type>. Можно было бы ожидать, что<ret>это исправит, но для нулевого лямбда-функтора, который возникает в результате выражения связывания (последняя линия выше), это не работает. Шаблоны вычета обратного типа создаются мгновенно, даже если это не требуется, и в результате возникает ошибка компиляции.

Решение этой проблемы заключается не в использовании функции<ret>, а в определении типа возврата как явно указанного параметра шаблона в вызове<bind>:

bind<int>(f, 1);       // ok

Ламбда-функторы, созданные с помощью<ret<T>>(bind<arg-list>))и<bind<T>><arg-list>], имеют точно такую же функциональность и #8212; за исключением того факта, что для некоторых нулевых лямбда-функторов первый не работает, в то время как последний делает.

Delaying constants and variables

Унарные функции<constant>,<constant_ref>и<var>превращают свой аргумент в лямбда-функтор, реализующий отображение идентичности. Первые два относятся к константам, вторые — к переменным. Использование этихотсроченныхконстант и переменных иногда необходимо из-за отсутствия явного синтаксиса для лямбда-выражений. Например:

for_each(a.begin(), a.end(), cout << _1 << ' ');
for_each(a.begin(), a.end(), cout << ' ' << _1);

Первая линия выводит элементы<a>, разделенные пространствами, в то время как вторая линия выводит пространство, за которым следуют элементы<a>без каких-либо разделителей. Причина этого в том, что ни один из операндов<cout << ' '>не является лямбда-выражением, поэтому<cout << ' '>оценивается немедленно. Чтобы отсрочить оценку<cout << ' '>, один из операндов должен быть явно обозначен как лямбда-выражение. Это достигается с помощью функции<constant>:

for_each(a.begin(), a.end(), cout << constant(' ') << _1);

Звонок<constant(' ')>создает нулевой лямбда-функтор, который сохраняет константу символа<' '>и возвращает ссылку на него при вызове. Функция<constant_ref>аналогична, за исключением того, что она сохраняет постоянную ссылку на свой аргумент.<constant>и<consant_ref>необходимы только тогда, когда вызов оператора имеет побочные эффекты, как в приведенном выше примере.

Иногда необходимо отложить оценку переменной. Предположим, мы хотим вывести элементы контейнера в пронумерованном списке:

int index = 0; 
for_each(a.begin(), a.end(), cout << ++index << ':' << _1 << '\n');
for_each(a.begin(), a.end(), cout << ++var(index) << ':' << _1 << '\n');

Первое<for_each>призвание не делает того, что мы хотим;<index>увеличивается только один раз, и его значение записывается в выходной поток только один раз. Используя<var>, чтобы сделать<index>выражение лямбда, мы получаем желаемый эффект.

В сумме<var(x)>создает нулевой лямбда-функтор, который хранит ссылку на переменную<x>. Когда лямбда-функтор вызывается, ссылка на<x>возвращается.

Naming delayed constants and variables

Можно заранее определить и назвать отсроченную переменную или постоянную вне лямбда-выражения. Для этой цели служат шаблоны<var_type>,<constant_type>и<constant_ref_type>. Они используются как:

var_type<T>::type delayed_i(var(i));
constant_type<T>::type delayed_c(constant(c));

Первая строка определяет переменную<delayed_i>, которая является отсроченной версией переменной<i>типа<T>. Аналогично, вторая строка определяет константу<delayed_c>как отложенную версию константы<c>. Например:

int i = 0; int j;
for_each(a.begin(), a.end(), (var(j) = _1, _1 = var(i), var(i) = var(j))); 

эквивалентны:

int i = 0; int j;
var_type<int>::type vi(var(i)), vj(var(j));
for_each(a.begin(), a.end(), (vj = _1, _1 = vi, vi = vj));

Вот пример наименования отсроченной постоянной:

constant_type<char>::type space(constant(' '));
for_each(a.begin(),a.end(), cout << space << _1);

About assignment and subscript operators

Как описано вразделе под названием “ Операторы назначения и подписки”, операторы назначения и подписки всегда определяются как функции-члены. Это означает, что для выражений формы<x = y>или<x[y]>, которые следует интерпретировать как выражения лямбды, левый операнд<x>должен быть выражением лямбды. Поэтому иногда необходимо использовать<var>для этой цели. Мы повторяем пример израздела под названием “ Назначение и операторы субскриптов”:

int i; 
i = _1;       // error
var(i) = _1;  // ok

Обратите внимание, что операторы присвоения соединений<+=>,<-=>и т.д. могут быть определены как функции, не являющиеся членами, и, таким образом, они интерпретируются как выражения лямбда, даже если только правый операнд является выражением лямбда. Тем не менее, совершенно нормально явно задерживать левый операнд. Например,<i += _1>эквивалентно<var(i) += _1>.

Lambda expressions for control structures

BLL определяет несколько функций для создания лямбда-функторов, которые представляют структуры управления. Все они принимают лямбда-функторы в качестве параметров и возвращаются<void>. Чтобы начать с примера, следующий код выводит все четные элементы некоторого контейнера<a>:

for_each(a.begin(), a.end(), 
         if_then(_1 % 2 == 0, cout << _1));  

BLL поддерживает следующие шаблоны функций для структур управления:

if_then(condition, then_part)
if_then_else(condition, then_part, else_part)
if_then_else_return(condition, then_part, else_part)
while_loop(condition, body)
while_loop(condition) // no body case
do_while_loop(condition, body)
do_while_loop(condition) // no body case 
for_loop(init, condition, increment, body)
for_loop(init, condition, increment) // no body case
switch_statement(...)

Типы возврата всей конструкции управления лямбда-функтора составляют<void>, за исключением<if_then_else_return>, который обертывает вызов условному оператору.

condition ? then_part : else_part

Правила возврата для этого оператора несколько сложны. В основном, если ветви имеют один и тот же тип, то это тип возврата. Если тип ветвей отличается, одна ветвь, скажем типа<A>, должна быть конвертируемой в другую ветвь, скажем типа<B>. В этой ситуации тип результата<B>. Кроме того, если общий тип является lvalue, то возвратный тип также будет lvalue.

Задержанные переменные, как правило, являются обычным явлением в выражениях лямбда контрольной структуры. Например, здесь мы используем функцию<var>, чтобы превратить аргументы<for_loop>в выражения лямбды. Эффект кода заключается в добавлении 1 к каждому элементу двумерного массива:

int a[5][10]; int i;
for_each(a, a+5, 
  for_loop(var(i)=0, var(i)<10, ++var(i), 
           _1[var(i)] += 1));  

BLL поддерживает альтернативный синтаксис для управляющих выражений, предложенный Жоэлем де Гусманном. Перегружая<operator[]>, можно получить более близкое сходство со встроенными структурами управления:

if_(condition)[then_part]
if_(condition)[then_part].else_[else_part]
while_(condition)[body]
do_[body].while_(condition)
for_(init, condition, increment)[body]

Например, используя этот синтаксис, пример<if_then>может быть записан как:

for_each(a.begin(), a.end(), 
         if_(_1 % 2 == 0)[ cout << _1 ])  

По мере накопления опыта мы можем в конечном итоге обесценить то или иное из этих синтаксисов.

Switch statement

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

switch_statement(condition, 
  case_statement<label>(lambda expression),
  case_statement<label>(lambda expression),
  ...
  default_statement(lambda expression)
)

Аргумент<condition>должен быть лямбда-выражением, создающим лямбда-функтор с интегральным типом возврата. Различные случаи создаются с функциями<case_statement>и необязательным случаем по умолчанию с функцией<default_statement>. Ярлыки случая приведены как явно указанные шаблонные аргументы для<case_statement>функций, а утверждения<break>неявно являются частью каждого случая. Например,<case_statement<1>(a)>, где<a>является неким лямбда-функтором, генерирует код:

case 1: 
  evaluate lambda functor a; 
  break;

Функция<switch_statement>специализирована для до 9 заявлений о случаях.

В качестве конкретного примера, следующий код повторяется над некоторым контейнером<v>и оптутами& #8220;ноль& #8221;для каждого<0>,& #8220;один& #8221;для каждого<1& #8220;другой:<n>& #8221;для любого другого значения<n>. Обратите внимание, что после<switch_statement>секвенируется другое выражение лямбда для вывода разрыва линии после каждого элемента:

std::for_each(v.begin(), v.end(),
  ( 
    switch_statement(
      _1,
      case_statement<0>(std::cout << constant("zero")),
      case_statement<1>(std::cout << constant("one")),
      default_statement(cout << constant("other: ") << _1)
    ), 
    cout << constant("\n") 
  )
);

Exceptions

BLL предоставляет лямбда-функторы, которые бросают и ловят исключения. Ламбда-функторы для метания исключений создаются с унарной функцией<throw_exception>. Аргументом этой функции является исключение, которое должно быть брошено, или лямбда-функтор, который создает исключение, которое должно быть брошено. Ламбда-функтор для переброски исключений создается с нулевой функцией<rethrow>.

Выражения лямбда для обработки исключений несколько сложнее. Общая форма выражения лямбда для блоков «попробовать поймать» выглядит следующим образом:

try_catch(
  lambda expression,
  catch_exception<type>(lambda expression),
  catch_exception<type>(lambda expression),
  ...
  catch_all(lambda expression)
)

Первое выражение лямбда — пробный блок. Каждый<catch_exception>определяет блок улова, где явно указанный аргумент шаблона определяет тип исключения для улова. Выражение лямбда в<catch_exception>определяет действия, которые следует предпринять, если будет поймано исключение. Обратите внимание, что результирующие обработчики исключений улавливают исключения в качестве ссылок, т.е.<catch_exception<T>(...)>приводит к блоку улова:

catch(T& e) { ... }

Последний блок улова может быть вызовом<catch_exception<type>>или<catch_all>, что является выражением лямбда, эквивалентным<catch(...)>.

Пример 18.1, “ Бросать и обрабатывать исключения в выражениях лямбда. ”демонстрирует использование инструментов обработки исключений BLL. Первый обработчик ловит исключения типа<foo_exception>. Обратите внимание на использование<_1>заполнителя в корпусе обработчика.

Второй обработчик показывает, как выбрасывать исключения, и демонстрирует использованиезаполнителя исключения<_e>. Это специальный заполнитель, который относится к пойманному объекту исключения в теле обработчика. Здесь мы имеем дело с исключением типа<std::exception>, которое несет строку, объясняющую причину исключения. Это объяснение можно задать с помощью функции члена с нулевым аргументом<what>. Выражение<bind(&std::exception::what, _e)>создает функцию лямбда для совершения этого вызова. Обратите внимание, что<_e>не может быть использовано за пределами выражения обработчика исключений. Последняя линия второго обработчика конструирует новый объект исключения и бросает его<throw exception>. Конструирование и разрушение объектов в выражениях лямбды объясняется вразделе под названием & #8220; Строительство и разрушение & #8221;

Наконец, третий обработчик<catch_all>демонстрирует отбрасывание исключений.

Example 18.1. Throwing and handling exceptions in lambda expressions.

for_each(
  a.begin(), a.end(),
  try_catch(
    bind(foo, _1),                 // foo may throw
    catch_exception<foo_exception>(
      cout << constant("Caught foo_exception: ") 
           << "foo was called with argument = " << _1
    ),
    catch_exception<std::exception>(
      cout << constant("Caught std::exception: ") 
           << bind(&std::exception::what, _e),
      throw_exception(bind(constructor<bar_exception>(), _1)))
    ),      
    catch_all(
      (cout << constant("Unknown"), rethrow())
    )
  )
);

Construction and destruction

Операторы<new>и<delete>могут быть перегружены, но их типы возврата фиксированы. В частности, типы возврата не могут быть лямбда-функторами, что предотвращает их перегрузку для лямбда-выражений. Невозможно взять адрес конструктора, поэтому конструкторы не могут использоваться в качестве целевых функций в выражениях связей. То же самое касается и деструкторов. Как способ обойти эти ограничения, BLL определяет классы обертки для вызовов<new>и<delete>, а также для конструкторов и деструкторов. Случаи этих классов являются функциональными объектами, которые могут быть использованы в качестве целевых функций связывающих выражений. Например:

int* a[10];
for_each(a, a+10, _1 = bind(new_ptr<int>())); 
for_each(a, a+10, bind(delete_ptr(), _1));

Выражение<new_ptr<int>()>создает объект функции, который вызывает<new int()>при вызове, и обертывание, которое внутри<bind>, делает его лямбда-функтором. Таким же образом выражение<delete_ptr()>создает объект функции, который вызывает<delete>на свой аргумент. Обратите внимание, что<new_ptr<T>>()также могут принимать аргументы. Они передаются непосредственно конструктору и, таким образом, позволяют звонить конструкторам, которые принимают аргументы.

В качестве примера вызовов конструктора в лямбда-выражениях следующий код читает целые числа из двух контейнеров<x>и<y>, конструирует из них пары и вставляет их в третий контейнер:

vector<pair<int, int> > v;
transform(x.begin(), x.end(), y.begin(), back_inserter(v),
          bind(constructor<pair<int, int> >(), _1, _2));

Table 18.1, “Construction and destruction related function objects.” lists all the function objects related to creating and destroying objects, showing the expression to create and call the function object, and the effect of evaluating that expression.

Table 18.1. Construction and destruction related function objects.

Function object callWrapped expression
<constructor<T>()(arg_list>T<arg_list>
<destructor()(a)>a.~A(), where a is of type A
<destructor()(pa)>pa->~A(), where pa is of type A*
<new_ptr<T>()(arg_list><new T(arg_list>
<new_array<T>()(sz)><new T[sz]>
<delete_ptr()(p)><delete p>
<delete_array()(p)><delete p[]>

Special lambda expressions

Preventing argument substitution

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

Аргументами для связывающего выражения могут быть произвольные лямбда-выражения, например, другие связывающие выражения. Например:

int foo(int); int bar(int);
...
int i;
bind(foo, bind(bar, _1))(i);

Последняя строка звонит<foo(bar(i));>. Обратите внимание, что первый аргумент в выражении связывания, целевой функции, не является исключением и, таким образом, может быть выражением связывания. Самый внутренний лямбда-функтор просто должен вернуть то, что можно использовать в качестве целевой функции: другой лямбда-функтор, указатель функции, указатель на функцию члена и т. д. Например, в следующем коде самый внутренний лямбда-функтор делает выбор между двумя функциями и возвращает указатель на одну из них:

int add(int a, int b) { return a+b; }
int mul(int a, int b) { return a*b; }
int(*)(int, int)  add_or_mul(bool x) { 
  return x ? add : mul; 
}
bool condition; int i; int j;
...
bind(bind(&add_or_mul, _1), _2, _3)(condition, i, j);

Unlambda

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

Рассмотрим следующий шаблон функций:

template<class F>
int nested(const F& f) {
  int x;
  ...
  bind(f, _1)(x);
  ...
}

Где-то внутри функции формальный параметр<f>используется в качестве целевой функции в экспрессии связывания. Для того чтобы этот призыв<bind>был действительным,<f>должна быть унарная функция. Допустим, два следующих вызова<nested>:

int foo(int);
int bar(int, int);
nested(&foo);
nested(bind(bar, 1, _1));

Оба являются унарными функциями или функциональными объектами с соответствующими типами аргументов и возвратов, но последние не будут компилироваться. В последнем вызове связующим выражением внутри<nested>станет:

bind(bind(bar, 1, _1), _1) 

Когда это вызывается<x>, после замещения мы в конечном итоге пытаемся вызвать

bar(1, x)(x)

Что является ошибкой. Призыв к<bar>возвращает int, а не унарную функцию или функциональный объект.

В приведенном выше примере намерение выражения связывания в функции<nested>состоит в том, чтобы рассматривать<f>как обычный функциональный объект вместо лямбда-функтора. BLL предоставляет шаблон функции<unlambda>, чтобы выразить это: лямбда-функтор, завернутый внутри<unlambda>, больше не является лямбда-функтором и не участвует в процессе замены аргумента. Обратите внимание, что для всех других типов аргументов<unlambda>является операцией идентификации, за исключением создания неконстовых объектов const.

Используя<unlambda>,<nested>функция записывается как:

template<class F>
int nested(const F& f) {
  int x;
  ...
  bind(unlambda(f), _1)(x);
  ...
}

Protect

Функция<protect>связана с unlambda. Он также используется для предотвращения замены аргументов, но в то время как<unlambda>превращает лямбда-функтор в обычный функциональный объект для добра,<protect>делает это временно, всего за один раунд оценки. Например:

int x = 1, y = 10;
(_1 + protect(_1 + 2))(x)(y);

Первый вызов заменяет<x>самый левый<_1>и приводит к другому лямбда-функтору<x + (_1 + 2)>, который после вызова с<y>становится<x + (y + 2)>, и таким образом, наконец, 13.

Основная мотивация для включения<protect>в библиотеку заключалась в том, чтобы разрешить вложенные вызовы алгоритма STL (раздел под названием & #8220; Введение вызовов алгоритма STL & #8221;).

Rvalues as actual arguments to lambda functors

Фактические аргументы в пользу лямбда-функторов не могут быть неконстовыми значениями. Это связано с продуманным дизайнерским решением: либо у нас есть это ограничение, либо не может быть побочных эффектов к фактическим аргументам. Есть способы обойти это ограничение. Мы повторяем пример из разделараздела под названием “ О фактических аргументах к лямбда-функторам”и перечисляем различные решения:

int i = 1; int j = 2; 
(_1 + _2)(i, j); // ok
(_1 + _2)(1, 2); // error (!)

  1. Если r-значение относится к классному типу, то возвращаемый тип функции, создающей r-значение, следует определять как const. Из-за неудачного языкового ограничения это не работает для встроенных типов, поскольку встроенные значения не могут быть квалифицированы.

  2. Если вызов функции лямбда доступен, функцияmake_constможет использоваться дляконституированиязначения r. Например:

    (_1 + _2)(make_const(1), make_const(2)); // ok
    

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

  3. Если ни один из вышеперечисленных вариантов невозможен, выражение лямбда может быть завернуто в функцию<const_parameters>. Он создает другой тип лямбда-функтора, который принимает свои аргументы за ссылки. Например:

    <
    const_parameters(_1 + _2)(1, 2); // ok
    
    >

    Обратите внимание, что<const_parameters>приводит все аргументы против. Следовательно, в случае, если один из аргументов является неконстовым значением, а другой аргумент должен быть принят в качестве неконстовой ссылки, этот подход не может быть использован.

  4. Если ничего из вышеперечисленного не представляется возможным, остается одно решение, которое, к сожалению, может нарушить правильность. Решением является еще одна обертка из лямбда-функтора, которую мы назвалиbreak_const, чтобы предупредить пользователя о потенциальных опасностях этой функции. Функцияbreak_constсоздает лямбда-функтор, который принимает свои аргументы за const и отбрасывает константу до вызова оригинального обернутого лямбда-функтора. Например:

    int i; 
    ...
    (_1 += _2)(i, 2);                 // error, 2 is a non-const rvalue
    const_parameters(_1 += _2)(i, 2); // error, i becomes const
    break_const(_1 += _2)(i, 2);      // ok, but dangerous
    

    Обратите внимание, что результатыbreak_constилиconst_parametersне являются лямбда-функторами, поэтому они не могут использоваться в качестве подвыражений лямбда-выражений. Например:

    break_const(_1 + _2) + _3; // fails.
    const_parameters(_1 + _2) + _3; // fails.
    

    Однако такого рода код никогда не должен быть необходим, так как вызовы на сублямбда-функторы производятся внутри BLL, и на них не влияет неконст-значение.

Casts, sizeof and typeid

Cast expressions

BLL определяет свои аналоги для четырех литых выражений<static_cast>,<dynamic_cast>,<const_cast>и<reinterpret_cast>. Версии BLL литых выражений имеют приставку<ll_>. Тип отливки дается в качестве явно указанного шаблонного аргумента, и единственным аргументом является выражение, из которого выполняется отливка. Если аргумент является лямбда-функтором, то сначала оценивается лямбда-функтор. Например, следующий код использует<ll_dynamic_cast>для подсчета числа<derived>экземпляров в контейнере<a>:

class base {};
class derived : public base {};
vector<base*> a;
...
int count = 0;
for_each(a.begin(), a.end(), 
         if_then(ll_dynamic_cast<derived*>(_1), ++var(count)));

Sizeof and typeid

Для этих выражений используются аналоги BLL<ll_sizeof>и<ll_typeid>. Оба принимают один аргумент, который может быть выражением лямбда. Ламбда-функтор создает обертывание вызова<sizeof>или<typeid>, и когда лямбда-функтор называется, выполняется обернутая операция. Например:

vector<base*> a; 
...
for_each(a.begin(), a.end(), 
         cout << bind(&type_info::name, ll_typeid(*_1)));

Здесь<ll_typeid>создается лямбда-функтор для вызова<typeid>для каждого элемента. Результатом вызова<typeid>является экземпляр класса<type_info>, и выражение связывания создает лямбда-функтор для вызова функции члена этого класса<name>.

Nesting STL algorithm invocations

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

int a[100][200];
int sum = 0;
std::for_each(a, a + 100, 
	      bind(ll::for_each(), _1, _1 + 200, protect(sum += _1)));

BLL-версии алгоритмов STL представляют собой классы, которые определяют оператор вызова функции (или несколько перегруженных) для вызова соответствующих шаблонов функций в пространстве имен<std>. Все эти структуры размещаются в подименном пространстве<boost::lambda:ll>.

Обратите внимание, что нет простого способа выразить перегруженный вызов функции члена в выражении лямбда. Это ограничивает полезность вложенных алгоритмов STL, поскольку, например, функция<begin>имеет более одного перегруженного определения в шаблонах контейнеров. В общем, что-то похожее на псевдокод ниже не может быть написано:

std::for_each(a.begin(), a.end(), 
	      bind(ll::for_each(), _1.begin(), _1.end(), protect(sum += _1)));

Тем не менее, может быть оказана помощь в особых случаях. BLL определяет два класса объектов вспомогательных функций,<call_begin>и<call_end>, которые обертывают вызов в<begin>и, соответственно,<end>функции контейнера и возвращают<const_iterator>тип контейнера. С помощью этих шаблонов-помощников вышеуказанный код становится:

std::for_each(a.begin(), a.end(), 
	      bind(ll::for_each(), 
                   bind(call_begin(), _1), bind(call_end(), _1),
                        protect(sum += _1)));


PrevUpHomeNext

Статья Lambda expressions in details раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 18. Boost.Lambda может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: Chapter 18. Boost.Lambda ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 17:10:30/0.043365955352783/1