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

В строке 4 аргументу присваивается функциональный объект, в строке 5 – лямбда-выражение.

Отметим, что в универсальном аргументе лямбда-выражение сохраняется также просто, как и любой другой тип. Это связано с тем, что как оператор присваивания (operator = класса UniArgument, Листинг 45 п. 4.5.1), так и класс для хранения аргументов вызова (CallableObject, там же) реализованы в виде шаблонов. Когда мы вызываем указанный оператор, передавая ему лямбда-выражение, компилятор неявно выведет тип параметра шаблона из переданного аргумента, подобно тому, как это происходит в шаблонной функции для синхронных вызовов. В свою очередь, внутри оператора с помощью new динамически создается экземпляр CallableObject, инстанциированный соответствующим выведенным типом. Таким образом, явно указывать тип передаваемого аргумента не требуется, компилятор выводит его сам.

4.5.2. Настройка сигнатуры

До сих пор мы предполагали, что функция, реализующая обратный вызов, имеет тип void и на вход принимает только одно значение eventID, и исходя из этого, делали обратный вызов. А если выясняется, что функция должна иметь дополнительные параметры, нам придется изменять реализацию универсального аргумента и объектов, с ним связанных? А если нам необходимы инициаторы, которые используют функции с различными сигнатурами? Теперь что, для каждой сигнатуры придется реализовать отдельный аргумент? Есть другой путь: настройка сигнатуры вызова через параметры шаблона. Для ее реализации используется частичная специализация шаблона в сочетании с переменным числом параметров (partial template specialization, variadic templates), пример представлен в Листинг 47.

Листинг 47. Настройка сигнатуры

//General specialization

template <typename unused>  // (1)

class function;

//Partial specialization

template<typename Return, typename … ArgumentList >  // (2)

class function<Return(ArgumentList…)>

{

public:

  Return operator()(ArgumentList… arguments)  // (3)

  {

  }

};

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

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

Описанная реализация всего лишь демонстрирует настройку сигнатуры. Практической пользы от нее немного, потому что тело перегруженного оператора пустое, и вызов осуществлен не будет. Используя описанную технику, добавим настройку сигнатуры к аргументу, реализующему стирание типов (Листинг 48).

Листинг 48. Стирание типов с настройкой сигнатуры

template <typename unused>

class UniArgument;

template<typename Return, typename … ArgumentList>

class UniArgument<Return(ArgumentList…)>  // (1)

{

private:

  struct Callable

  {

    virtual Return operator()(ArgumentList… arguments) = 0;  // (3)

  };

  std::unique_ptr<Callable> callablePointer;

  template <typename Argument>

  struct CallableObject : Callable

  {

    Argument storedArgument;

    CallableObject(Argument argument) : storedArgument(argument) { }

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

    {

      //return storedArgument(arguments…);

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

    }

  };

public:

  Return operator() (ArgumentList… arguments)        // (10)

  {

    return callablePointer->operator()(arguments…);  // (11)

  }

  template <typename Argument>

  void operator = (Argument argument)

  {

    callablePointer.reset(new CallableObject<Argument>(argument));

  }

};

По сравнению с реализацией для фиксированной сигнатуры (Листинг 45 п. 4.5.1) изменения здесь следующие. Класс аргумента (строка 1) объявляется в виде шаблона. Параметрами шаблона выступают Return – тип значения, возвращаемого функцией, и ArgumentList – пакет параметров, определяющих типы передаваемых в функцию аргументов. При объявлении перегруженных операторов (строки 3, 8, 10), вместо конкретного типа возвращаемого значения подставляется параметр шаблона Return, вместо конкретных типов входных параметров подставляется ArgumentList. В местах, где происходит вызов оператора, пакет параметров раскрывается (строки 9 и 11), что означает, что вместо arguments будет подставлен список переменных с типами, заданными в пакете параметров.