Выбрать главу

Теперь в универсальном аргументе можно настраивать сигнатуру, как это продемонстрировано в Листинг 49.

Листинг 49. Использование аргумента с настройкой сигнатуры

void ExternalHandler1(int eventID) {/*Do something*/}            // (1)

int  ExternalHandler2(int eventID, int contextID) { return 0; }  // (2)

struct CallbackHandler  // (3)

{

  void operator() (int eventID) {}

  bool operator() (int eventID, int contextID) { return false; }

};

int main()

{

  int capturedValue = 100;

  CallbackHandler callbackObject;           // (4)

  UniArgument<void(int)> argument1;         // (5)

  UniArgument<bool(int, int)> argument2;    // (6)

  argument1 = ExternalHandler1;  // (7)

  argument2 = ExternalHandler2;  // (8)

  argument1 = callbackObject;    // (9)

  argument2 = callbackObject;    // (10)

  argument1 = [capturedValue](int eventID) {/*Do something*/};                           // (11)

  argument2 = [capturedValue](int eventID, int contextID) { /*DoSomething*/return 0; };  // (12)

  argument1(3);               // (13)

  int res = argument2(4, 5);  // (14)

  return res;

}

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

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

В строках 7 и 8 в аргумент передаются внешние функции. В строках 9 и 10 передается функциональный объект, у которого, в зависимости от настроенной сигнатуры будет вызван соответствующий перегруженный оператор. В строках 11 и 12 передаются лямбда-выражения. В строках 13 и 14 осуществляются вызовы в соответствии с заданной сигнатурой.

4.5.3. Вызов метода класса

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

До появления стандарта C++17 реализация указанного способа была достаточно сложной: пришлось бы объявлять еще один объект, который наследовался от Callable и осуществлял вызов метода; для создания соответствующего объекта пришлось бы объявить дополнительный перегруженный оператор присваивания, который в качестве входного аргумента принимал указатель на метод. Но в новом стандарте появилась функция std::invoke, которая определяет тип принимаемого объекта вызова и осуществляет вызов для соответствующего типа. Таким образом, для поддержки вызова метода класса необходимо в реализации CallableObject изменить одну-единственную строчку:

Return operator() (ArgumentList… arguments) override  // (8)

{

  //return storedArgument(arguments…);

  return std::invoke(storedArgument, arguments…);  // (9)

}

На удивление просто, не правда ли?

Использование универсального аргумента для вызова метода класса представлено в Листинг 50.

Листинг 50. Использование универсального аргумента для вызова метода класса

struct CallbackHandler

{

  void handler1(int eventID) {};

  bool handler2(int eventID, int contextID) { return false; };

};

int main()

{

  CallbackHandler callbackObject;

  UniArgument<void(CallbackHandler*, int)> argument1;       // (1)

  UniArgument<bool(CallbackHandler*, int, int)> argument2;  // (2)

  argument1 = &CallbackHandler::handler1;  // (3)

  argument2 = &CallbackHandler::handler2;  // (4)

  argument1(&callbackObject, 100);         // (5)

  argument2(&callbackObject, 0, 1);        // (6)

}

В строках 1 и 2 объявлены универсальные аргументы для вызова соответствующих методов класса. Как видим, в сигнатуре функции первый параметр является типом класса, для которого будут вызываться соответствующие методы. В строках 3 и 4 производится настройка методов, в строках 5 и 6 – вызовы методов для экземпляра соответствующего класса.