C что из себя представляет std function
Перейти к содержимому

C что из себя представляет std function

  • автор:

C что из себя представляет std function

При использовании объектов-функций и лямбда-выражений следует помнить, что они не эквивалентны указателям на функции. Чтобы упростить совместное использование указателей на функции, объектов-функций и лямбда-выражений модуль определяет шаблон std::function<> . std::function представляет нечто, что может вызываться как функция — это может быть и указатель на функцию, и объект-функция, и лямбда-выражение. Рассмотрим, как мы можем его использовать:

#include #include // для std::function class Sum < public: int operator()(int a, int b)< return a + b;>>; int subtract(int a, int b) int main() < std::functionoperation; operation = Sum<>; // operation представляет объект-функцию Sum std::cout ; operation = multiply; // operation представляет лямбда-выражение multiply std::cout 

Для использования типа std::function прежде всего необходимо подключить заголовочный файл

#include

В функции main определяем переменную std::function :

std::function operation;

Переменная operation предсталяет некоторую функцию, которая имеет два параметра типа int и возвращает также значение типа int.

Мы можем присвоить этой переменной объект-функцию:

operation = Sum<>;
operation = subtract;
auto multiply = [](int a, int b); operation = multiply;

Для вызова все этих функций вызываем operation как функцию, передавая ее параметрам некоторые значения:

operation(10, 6)
#include #include // для std::function class Console < public: void operator()(const std::string& message) < std::cout >; void print(const std::string& message) < std::cout int main() < std::functionaction; action = Console<>; action("Word"); action = print; action("Work"); auto log = [](const std::string& mes); action = log; action("World"); >

Здесь переменная action представляет некоторую функцию, которая принимает строку и ничего не возвращает — std::function

Применения std::function

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

30 января 2019

| �� 12 мин

#Статья об использовании std::function

Поводом для написания данной статьи стало желание систематизировать всё то, чему я научился в процессе кодинга лаб по таким предметам, как Численные методы; Уравнения математической физики; Методы оптимизации. Надеюсь эта статья будет полезна не только людям, которые учатся в НГТУ на факультете ФПМИ, но и широкому кругу читателей.

#Введение

В Си/C++ имеется возможность передавать функцию в качестве аргумента функции как указатель на функцию. Вы наверняка знаете этот страшный синтаксис:

int add(int x, int y)  return x+y; > void call_operation(int (*operation)(int, int))  int c = operation(1, 2); > call_operation(add); 

Начиная с C++11 появился удобный интерфейс для описания функциональных объектов под названием std::function , он имеет более красивый синтаксис:

#include int add(int x, int y)  return x+y; > int call_operation(std::functionint(int, int)> operation)  return operation(1, 2); > int c = call_operation(add); // c = 3 

Причём, в отличие от Си, в C++ таким образом можно описывать любой функциональный объект, то есть объект, допускающий вызов операции () :

// . struct Adder   int operator()(int a, int b)  return state + a + b; > int state; >; Adder add = 1>; int d = call_operation(add); // d = 4 

Аналогичным образом можно использовать лямбды:

// . int state = 2; int e = call_operation([&](int a, int b) -> int   return state + a + b; >); // e = 5 

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

#Численное вычисление производной

Пусть у нас имеется некоторая функция f , которая получает double и возвращает double . Тогда можно численно вычислить производную этой функции следующим образом:

double f(double x); double derivative_f(double x)  double h = 0.0001; return (f(x+h)-f(x))/h; > 

Но писать для каждой функции другую функцию, вычисляющую её производную непрактично! Поэтому такую проблему можно решить с помощью std::function :

std::functiondouble(double)> derivative(std::functiondouble(double)> f)  return [f] (double x) -> double   double h = 0.0001; return (f(x+h)-f(x))/h; > > 

Данная функция получает функцию, и возвращает функцию, которая считает её производную. Пример использования:

double f(double x)  return x*x; > auto df = derivative(f); double a = f(1); // a == 1 double a = df(1); // a == 2 

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

typedef std::functiondouble(double)> function_1d_t; function_1d_t calc_first_derivative(const function_1d_t& f)  return [f](double x) -> double   const double h = 0.001; return (-f(x + 2 * h) + 8 * f(x + h) - 8 * f(x - h) + f(x - 2 * h)) / (12 * h); >; > function_1d_t calc_second_derivative(const function_1d_t& f)  return [f](double x) -> double   const double h = 0.001; return (-f(x+2*h) + 16*f(x+h) - 30*f(x) + 16*f(x-h) - f(x-2*h))/(12*h*h); >; > 

Может показаться, что если уменьшить величину 0.001 , то точность повысится, но это не так, на практике у меня точность только падала, а при этом значении уже очень хорошо расчитывается производная.

#Пример использования std::bind

Окей, у нас существует функция для расчета производной одномерной функции, но что если у нас функция принимает два аргумента, например f(x, y) . Как посчитать её производную по одному параметру?

На помощь приходит std::bind — функция, которая получает функцию, список аргументов и возвращает другую функцию. И лучше увидеть пример, чтобы понять как он работает:

double f(double x, double y)  return 2*x + 3*y; > // Производная f по x при y=0 auto fx = calc_first_derivative(std::bind(f, std::placeholders::_1, 0)); // Производная f по y при x=0 auto fy = calc_first_derivative(std::bind(f, 0, std::placeholders::_1)); double fx_value = fx(1); // fx_value = 2; double fy_value = fy(1); // fy_value = 3; 

Где std::placeholders::_1 показывает первый параметр внешней функции, которую возвратит std::bind .

К сожалению в этом случае функция fx получается функцией одной переменной, при фиксированном y=0 . Чтобы получить функцию fx , которая принимает два параметра, но при этом возвращает производную по x , можно воспользоваться следующим трюком:

auto fx = [] (double x, double y) -> double   return calc_first_derivative(std::bind(f, std::placeholders::_1, y))(x); >; double fx_value = fx(1, 0); // df(1, 0)/dx 

Наверное можно было бы его улучшить, чтобы не копипастить эти строки, и придумать новый std::bind2 , при помощи шаблонной магии C++, но я такое не умею. Если вы сможете это сделать, обязательно pull-request’ните сюда!

Или другой пример:

double f(double x, double y, double t); using namespace std::placeholders; std::functiondouble(double, double)> g = std::bind(f, _2, 5, _1); double gv = g(1, 2); double fv = f(2, 5, 1); // gv == fv 

#Автоматический расчет правой части

В таком предмете, как УМФ требуется решить следующее дифференциальное уравнение:

-div(lambda * grad u) + gamma * u + sigma * du/dt = f ,

где u = u(x, y, t) является неизвестной функцией. Для численного решения используется Метод Конечных Элементов.

Для декартовых координат это уравнение раскладывается в lambda*(d^2 u/dx^2 + d^2 u/dy^2) + gamma * u + sigma * du/dt = f

Для тестирования программы-решателя мы придумываем некоторую функцию u , например: u = x*x + y*y + t , придумываем значения констант lambda , gamma , sigma , и согласно верхнему уравнению можно вычислить функцию f , при которой дифференциальное уравнение превращается в тождество.

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

typedef std::functiondouble(double, double, double)> function_3d_t; /** Все константы решаемого уравнения. */ struct constants_t   double lambda; /// Коэффициент внутри div double gamma; /// Коэффициент при u double sigma; /// Коэффициент при du/dt >; function_3d_t calc_right_part( const function_3d_t& u, const constants_t& cs )  // f = -div(lambda * grad u) + gamma * u + sigma * du/dt return [=](double x, double y, double t) -> double   using namespace placeholders; auto ut = calc_first_derivative(bind(u, x, y, _1)); auto uxx = calc_second_derivative(bind(u, _1, y, t)); auto uyy = calc_second_derivative(bind(u, x, _1, t)); return -cs.lambda * (uxx(x) + uyy(y)) + cs.gamma * u(x, y, t) + cs.sigma * ut(t); >; > 

Код взят опять же из репозитория по УМФ.

Таким образом, мы получаем функцию f на основе известной нам функции u .

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

#Обертка для подсчета вызовов функции

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

Это можно сделать не вмешиваясь в код этого метода, явным образом выставляя count++ при каждом вызове функции, послав вместо функции обёртку над ней:

typedef std::functiondouble(const Vector&)> Function; Function setFunctionToCountCalls(int* where, const Function& f)  (*where) = 0; return [where, f](const Vector& v) -> double  (*where)++; return f(v); >; > double f(const Vector& v)  // . > int fCount = 0; auto result = optimize(/* . */, setFunctionToCountCalls(&fCount, f), /* . */); 

#Замер времени

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

#include using namespace chrono; auto start = high_resolution_clock::now(); // . // main code // . auto end = high_resolution_clock::now(); double time = duration_cast(end - start).count(); 

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

#include #include /** Считает время выполнения функции f в микросекундах. */ inline double calc_time_microseconds(const std::functionvoid(void)>& f)  using namespace std::chrono; auto start = high_resolution_clock::now(); f(); auto end = high_resolution_clock::now(); return duration_cast(end - start).count();; > double time = calc_time_microseconds([&]()  // . // main code // . >); 

Притом мы не теряем локальные переменные благодаря использованию лямбд.

#Двойной интеграл

Предположим, что у нас есть функция вычисления интеграла одномерной функции:

#include typedef std::functiondouble(double)> function_1d_t; double calc_integral_gauss3( double a, double b, int n, // n - количество внутренных узлов const function_1d_t& f ); 

Тогда двойной интеграл двумерной функции можно вычислить следующим образом:

double calc_integral_gauss3( double ax, double bx, int nx, double ay, double by, int ny, const function_2d_t& f )  return calc_integral_gauss3(ax, bx, nx, [ay, by, ny, f](double x)->double   return calc_integral_gauss3(ay, by, ny, bind(f, x, _1)); >); > 

#Функции тестирования

Представим, у нас есть множество методов, решающих одну задачу, но каждый по-разному, при этом интерфейс у них одинаковый. И стоит задача протестировать их все единым образом, построить таблицы. Это тоже можно красиво сделать с помощью std::function , написать прототип функции метода:

typedef std::function/* Method args. */)> Method; MethodResult method1(/* Method args. */); MethodResult method2(/* Method args. */); MethodResult method3(/* Method args. */); MethodResult method4(/* Method args. */); MethodResult method5(/* Method args. */); void makeTableForMethod( const Method& method, const std::string& file, /* Method args. */ )  // . > std::vector> methodsToTest =   "table1.txt">,  "table2.txt">,  "table3.txt">,  "table4.txt">,  "table5.txt">, >; for (auto& i : methodsToTest) makeTableForMethod(i.first, i.second, /* Method args. */); 

Некоторые люди делают это с помощью копипаста, но думаю не стоит вам объяснять чем плох копипаст по сравнению с этим подходом.

#Одномерный поиск

В МО многомерные методы нахождения минимума функции используют одномерную функцию оптимизации. И было задание задавать различные методы одномерной оптимизации, чтобы протестировать их эффективность конкретно в этой среде. Ну раз так, то мы не будем в программе жестко задавать функцию одномерной оптимизации, а будем передавать её как аргумент функции многомерной оптимизации:

typedef std::functiondouble(const double&)> OneDimensionFunction; typedef std::functiondouble(const OneDimensionFunction&, double, double, double)> OneDimenshionExtremumFinder; double oneDimensionOptimizator1(const OneDimensionFunction& f, double a, double b, double eps); double oneDimensionOptimizator2(const OneDimensionFunction& f, double a, double b, double eps); double oneDimensionOptimizator3(const OneDimensionFunction& f, double a, double b, double eps); double oneDimensionOptimizator4(const OneDimensionFunction& f, double a, double b, double eps); typedef std::functiondouble(const Vector&)> Function; MethodResult multiDimenshionOptimize1( const Function& f, const OneDimenshionExtremumFinder& argmin, const Vector& x0, const double& eps ); MethodResult multiDimenshionOptimize2( const Function& f, const OneDimenshionExtremumFinder& argmin, const Vector& x0, const double& eps ); 

#Паттерн listener

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

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

На самом деле я не использовал это на практике, а пример этого паттерна взял из библиотеки дифференциальной эволюции.

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

#Инкапсулируем с помощью std::function

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

vector_t solve_differential_equation( const function_3d_t& f, const grid_t& grid ); 

Возвращается вектор, потому что мы находим только аппроксимацию весов q , именно они и возвращаются в методе.

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

Поэтому мы должны каким-то образом внутри функции решения дифференциального уравнения выставлять краевые условия.

В нашем курсе УМФ мы не решаем реальные задачи, а лишь исследуем насколько хорошо метод справляется с известными нам функциями u , поэтому может появиться соблазн написать следующим образом:

vector_t solve_differential_equation( const function_3d_t& f, const grid_t& grid, const function_3d_t& true_function_u )  // doing smth // выставляем краевые условия с помощью функции u по краям области // делаем что-то дальше > 

Но это ужасный стиль! Зачем нам находить значение функции при помощи численных методов, которую мы уже знаем? Это выглядит как бред, поэтому более красивым может быть передавать функцию, которая выставляет краевые условия:

vector_t solve_differential_equation( const function_3d_t& f, const grid_t& grid, const boundary_setter_function_t& set_boundary_conditions )  // doing smth set_boundary_conditions(/* . */); // делаем что-то дальше > 

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

Так как вы можете использовать это на практике, более подробно смотрите код из курсовой по УМФ:

  • Прототип функции выставления краевых условий.
  • Сама функция выставления краевых условий на основе известной нам функции u.
  • Функция решения МКЭ.
  • Пример использования.

#Заключение

Было показано каким образом std::function позволит вам избавиться от лишнего копипаста и инкапсулировать код и данные и в принципе сделать код намного более абстрактным и красивым. Если вы придумали ещё одно интересное применение std::function , то пишите в комментариях!

Пишем свой std::function (boost::function)

Классы std::function и boost::function являются высокоуровневыми обертками над функциями и функциональными объектами. Объекты таких классов позволяют хранить и вызывать функции и функторы с заданной сигнатурой, что бывает удобно, например, при создании callback вызовов (например, мы можем регистрировать несколько обработчиков, и это могут быть как обычные функции, так и объекты с определенным оператором =)

Если вам интересно, каким образом реализуется данный функционал, то прошу под кат

Краткое введение, примеры использования

Если вы не знакомы с boost::function и std::function, то можно ознакомиться с ними здесь и здесь
std::function входит в стандарт языка c++11, и компиляторы gcc-4.7 и msvc-2012 его поддерживают (возможно, более ранние версии тоже имеют поддержку). В принципе, в рамках этой статьи можно считать реализацию от boost и стандартную полностью идентичными, так что пользоваться можно любой из реализаций

Собственно пример использования:

int func1() < return 0; >struct callable < int operator() () < return 1; >>; ///. boost::function x; x = func1; int res = x(); // вернет 0 в качестве результата callable c; x = c; res = x(); // вернет 1 в качестве результата
Переходим к самой реализации
  • Самый простой use-case — инициализировать наш объект указателем на функцию или объектом-функцией и вызвать оператор ()
  • Поддержка операторов присваивания и копирующего конструктора
  • Поддержка указателей на функции-члены
Самая простая реализация, понятие Type erasure

В основе реализации данного класса лежит паттерн Type Erasure, более доступно можно почитать здесь Его предназначение заключается в том, что мы можем «спрятать» за одним интерфейсом различные сущности (объекты, указатели и пр.), которые предоставляют сходные возможности (например, осуществить вызов функции с тремя аргументами). Type erasure также можно представить как мостик, который связывает полиморфизм времени исполнения (runtime polymorfism) и полиморфизм времени компиляции (compile-time polymorfism).

Итак, переходим к реализации.
Мы будем использовать variadic templates из стандарта C++11. Например, gcc поддерживает этот функционал аж с версии 4.3, поэтому можно смело им пользоваться.

Не будем оригинальны и назовем наш класс function. Очевидно, что класс будет шаблонный, также очевидно, что у него будет один параметр шаблона — сигнатура (тип) вызываемой функции. Общая реализация шаблона отсутствует, вся работа будет происходить в частичной специализации шаблона. Частичная специализация нужна для того чтобы мы могли использовать типы аргументов и возвращаемого значения из нашей сигнатуры.
Сама реализация:

template class function; template class function  < public: function() : mInvoker() <>template function(FunctionT f) : mInvoker(new free_function_holder(f)) <> ReturnType operator ()(ArgumentTypes . args) < return mInvoker->invoke(args . ); > private: class function_holder_base < public: function_holder_base() <>virtual ~function_holder_base() <> virtual ReturnType invoke(ArgumentTypes . args) = 0; >; typedef std::auto_ptr invoker_t; template class free_function_holder : public function_holder_base < public: free_function_holder(FunctionT func) : function_holder_base(), mFunction(func) <>virtual ReturnType invoke(ArgumentTypes . args) < return mFunction(args . ); >private: FunctionT mFunction; >; invoker_t mInvoker; >;

Класс function определяет оператор () соответствующий сигнатуре функции и передает управление методу invoke у класса function_holder_base. Этот класс имеет виртуальную функцию invoke, которая тоже совпадает с указанной сигнатурой (за исключением неявного параметра this).
Также класс function имеет шаблонный конструктор, который принимает один аргумент, в этом конструкторе создается наследник free_function_holder класса function_holder_base. Этот наследник является шаблонным классом — он сохраняет в себе переданный аргумент (обычно это функтор или указатель на функцию). В нем также определяется метод invoke, который вызывает сохраненный функтор с заданными аргументами.

  • Тип возвращаемого значения может быть любой (в том числе и void), то есть конструкция return mFunction(args . ); валидна даже в том случае, когда возвращаемое значение void. Это сделано специально для шаблонного метапрограммирования, чтобы не плодить специализации для типа void.
  • Аналогичным образом работают и variadic templates если количество входных аргументов равно 0 (функция без параметров).
  • Мы можем написать шаблонный класс, который является наследником какого-то базового класса (причем необязательно шаблонного) — на этом стоит весь паттерн Type erasure

В принципе, мы получили работоспособный аналог std::function и boost::function, мы вполне можем писать следующий код:

int func2(const int * x, int y) < return (*x) + y; >///. typedef function int_function_with_two_args_t; int_function_with_two_args_t f2(func2); int x = 10; cout 

Переходим к улучшениям нашего класса:

Копируем поведение обычного указателя на функцию — оператор присваивания и конструктор копирования

Для того чтобы скопировать или присвоить объект мы должны уметь копировать (клонировать) указатель на базовый класс function_holder_base. Для этого расширим интерфейс этого класса следующим образом:

 class function_holder_base < public: function_holder_base() <>virtual ~function_holder_base()<> virtual ReturnType invoke(ArgumentTypes . args) = 0; virtual std::auto_ptr clone() = 0; private: function_holder_base(const function_holder_base & ); void operator = (const function_holder_base &); >;

Делаем наш класс некопируемым (декларируем соответствующие оператор и конструктор в private секции), и добявляем метод clone, чтобы наследники сами определяли правильную стратегию клонирования.

Также, в класс function добавляются операторы присваивания и конструктор копирования:

 function(const function & other) : mInvoker(other.mInvoker->clone()) <> function & operator = (const function & other) < mInvoker = other.mInvoker->clone(); >

Здесь мы используем auto_ptr и его разрушающее присваивание.

Осталось написать реализацию метода clone в наследнике — free_function_holder:

 typedef free_function_holder self_type; virtual invoker_t clone()

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

int func1() < return 0; >///. typedef function int_function_t; int_function_t f1(func1); cout 

Переходим к заключительной части:

Добавляем поддержку указателей на функции-члены

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

При использовании boost (std) и нашей реализации function придерживаемся правила, что первым аргументом должен идти объект, к которому применяется указатель на метод, соответственно аргументы теперь делятся на два вида: сам объект, и аргументы метода. Соответственно, у нас есть гарантия, что количество аргументов строго больше 0, этим мы и воспользуемся далее:

 template class member_function_holder : public function_holder_base < public: typedef FunctionType ClassType::* member_function_signature_t; member_function_holder(member_function_signature_t f) : mFunction(f)<>virtual ReturnType invoke(ClassType obj, RestArgumentTypes . restArgs) < return (obj.*mFunction)(restArgs . ); >virtual invoker_t clone() < return invoker_t(new member_function_holder(mFunction)); >private: member_function_signature_t mFunction; >;

Реализация конструктора, который принимает указатель на метод тривиальна:

 template function(FunctionType ClassType::* f) : mInvoker(new member_function_holder(f)) <>

Мы сделали еще одного наследника function_holder_base специально для указателей на методы. Здесь используется особенность variadic templates: переменное количество типов (которое задается троеточием) можно расщепить на фиксированную часть, и остаток переменной длины (в котором количество типов меньше на размер фиксированной части). В конструкторе мы передаем в параметры шаблона фиксированную часть состоящую из одного типа — сигнатура функции, и произвольную часть — все остальное (это типы всех аргументов), а в реализации member_function_holder мы требуем, чтобы фиксированная часть состояла из двух элементов — сигнатура функции, класс, в котором находится наш метод, и аргументы непосредственно для вызова (здесь мы как раз используем указанную выше гарантию, что количество всех аргументов строго больше 0). Таким образом, мы сохраняем указатель на метод в конструкторе, и вызываем его в реализации метода invoke.

Отдельно хочется сказать про крайне неудобный и неинтуитивный способ декларирования типа «указатель на метод» и про способ вызова такого метода. По этому поводу в C++ FAQ есть предупреждение, о том как минимизировать количество головной боли при работе с указателями на методы (я полчаса гуглил, как записать правильно это выражение)

Вот и пример использования:

struct Foo < int smth(int x) < return x + 1; >>; ///. typedef function member_function_t; member_function_t f1 = &Foo::smth; Foo foo; cout  
Заключение

Благодаря поддержке variadic templates реализация function получилась довольно лаконичной, в том же boost из-за необходимости поддерживать старые компиляторы (в которых нет variadic templates) данный функционал реализован с помощью boost.preprocessor (и имеет ограничение на количество аргументов — по умолчанию 10, можно поменять, определив соответствующий define: BOOST_FUNCTION_MAX_ARGS). Грубо говоря, реализация сделана для функций с одним аргументом, а потом «клонирована» на большее количество аргументов с помощью препроцессорной магии.

Полностью пример находится здесь

  • std::function
  • c++
  • boost
  • template metaprogramming

С++ 11, std::function, неясность с параметрами шаблона

В документации на cppreference указаны некоторые интересности: Для начала определения простой функции и простой структуры, они нужны для примера, как леммы в теоремах:

struct Foo < Foo(int num) : num_(num) <>void print_add(int i) const < std::cout int num_; >; void print_num(int i)

Далее идет объявление:

std::function f_display = print_num; 

Интересует меня параметр шаблона, я подумал: "это тип функции? ", так как еще ни разу нигде не видел круглые скобки в параметре шаблона. Но далее идет еще одна интересность:

std::function f_add_display = &Foo::print_add; 

метод Foo::print_add принимает на вход int, что это за параметр шаблона Какой это тип? Т.е. какое общее название для таких типов? Там это не написано. И я это не могу нагуглить. Я понимаю что это объект назначения, он содержит вызов функции (указатель на метод и аргумент), но какую роль тут играют круглые скобки? Я не могу просто основываться на логике, тут нужно прочитать. Мне необязательно разжевывать, будет достаточно указать что гуглить чтобы знать об этом поподробнее, или просто дать ссылку на нужную литературу.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *