19.4.3. Использование функций-членов как вызываемых объектов

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

Поскольку указатель на член класса не является вызываемым объектом, нельзя непосредственно передать указатель на функцию-член алгоритму. Например, если необходимо найти первую пустую строку в векторе строк, вполне очевидный вызов не сработает:

auto fp = &string::empty; // fp указывает на функцию empty()

                          // класса string

// ошибка: для вызова через указатель на член класса следует

// использовать оператор .* или ->*

find_if(svec.begin(), svec.end(), fp);

Алгоритм find_if() ожидает вызываемый объект, но предоставляется указатель на функцию-член fp. Этот вызов не будет откомпилирован, поскольку код в алгоритме find_if() выполняет примерно такой оператор:

// проверяет применимость данного предиката к текущему элементу,

// возвращает true

if (fp(*it)) // ошибка: для вызова через указатель на член класса

             // следует использовать оператор ->*

Использование шаблона function для создания вызываемого объекта

Один из способов получения вызываемого объекта из указателя на функцию-член подразумевает использование библиотечного шаблона function (см. раздел 14.8.3):

function<bool (const string&)> fcn = &string::empty;

find_if(svec.begin(), svec.end(), fcn);

Здесь шаблону function указано, что empty() — это функция, которая может быть вызвана со строкой и возвращает значение типа bool. Обычно объект, для которого выполняется функция-член, передается неявному параметру this. Когда шаблон function используется при создании вызываемого объекта для функции-члена, следует преобразовать код так, чтобы сделать этот неявный параметр явным.

Когда объект шаблона function содержит указатель на функцию-член, класс function знает, что для вызова следует использовать соответствующий оператор указателя на член класса. Таким образом, можно предположить, что у функции find_if() будет код наподобие следующего:

// если it является итератором в функции find_if(), то *it - объект

// в заданном диапазоне

if (fcn(*it)) // fcn - имя вызываемого объекта в функции find_if()

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

// если it является итератором в функции find_if(), то *it - объект

// в заданном диапазоне

if (((*it).*p)()) // p - указатель на функцию-член в функции fcn

При определении объекта шаблона function следует указать тип функции, сигнатура которой определяет представляемые вызываемые объекты. Когда вызываемой объект является функцией-членом, первый параметр сигнатуры должен представить (обычно неявный) объект, для которого будет выполнена функция-член. Передаваемая шаблону function сигнатура должна определять, будет ли объект передан как указатель или как ссылка.

При определении вызываемого объекта fcn() было известно, что нужно вызвать функцию find_if() для последовательности строковых объектов. Следовательно, от шаблона function требовалось создать вызываемый объект, получающий объекты класса string. Если бы вектор содержал указатели на тип string, от шаблона function требовалось бы ожидать указатель:

vector<string*> pvec;

function<bool (const string*)> fp = &string::empty;

// fp получает указатель на string и использует оператор ->* для вызова

// функции empty()

find_if(pvec.begin(), pvec.end(), fp);

Использование шаблона mem_fn для создания вызываемого объекта

Чтобы использовать шаблон function, следует предоставить сигнатуру вызова члена, который предстоит вызвать. Но можно позволить компилятору вывести тип функции-члена при использовании другого библиотечного средства, шаблона mem_fn, определенного, как и шаблон function, в заголовке functional. Как и шаблон function, шаблон mem_fn создает вызываемый объект из указателя на член класса. В отличие от шаблона function, шаблон mem_fn выведет тип вызываемого объекта из типа указателя на член класса:

find_if(svec.begin(), svec.end(), mem_fn(&string::empty));

Здесь шаблон mem_fn(&string::empty) создает вызываемый объект, получающий строковый аргумент и возвращающий логическое значение.

Вызываемый объект, созданный шаблоном mem_fn, может быть вызван для объекта или указателя:

auto f = mem_fn(&string::empty); // f получает string или string*

f(*svec.begin()); // ok: передача объекта string; f использует .* для

                  // вызова empty()

f(&svec[0]);      // ok: передача указателя на string; f использует .->

                  // для вызова empty()

Фактически шаблон mem_fn можно считать как будто создающим вызываемый объект с перегруженным оператором вызова функции — один получает тип string*, а другой — string&.

Использование функции bind() для создания вызываемого объекта

Для создания вызываемого объекта из функции-члена можно также использовать функцию bind() (см. раздел 10.3.4):

// связать каждую строку из диапазона

// с неявным первым аргументом empty()

auto it = find_if(svec.begin(), svec.end(),

           bind(&string::empty, _1));

Подобно шаблону function, при использовании функции bind() следует сделать явным обычно неявный параметр функции-члена, представляющий объект, с которым будет работать функция-член. Подобно шаблону mem_fn, первый аргумент вызываемого объекта, создаваемого функцией bind(), может быть либо указателем, либо ссылкой на тип string:

auto f = bind(&string::empty, _1);

f(*svec.begin()); // ok: аргумент - строка f, использует .* для вызова

                  // функции empty()

f(&svec[0]); // ok: аргумент - указатель на строку f использует .->

             // для вызова функции empty()

Упражнения раздела 19.4.3

Упражнение 19.18. Напишите функцию, использующую алгоритм count_if() для подсчета количества пустых строк в заданном векторе.

Упражнение 19.19. Напишите функцию, получающую вектор vector<Sales_data> и находящую первый элемент, средняя цена которого превосходит заданное значение.