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

<p>4.5.2. Настройка сигнатуры</p>

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

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

//General specialization

template   // (1)

class function;

//Partial specialization

template  // (2)

class function

{

public:

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

  {

  }

};

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

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

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

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

template 

class UniArgument;

template

class UniArgument  // (1)

{

private:

  struct Callable

  {

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

  };

  std::unique_ptr callablePointer;

  template 

  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 

  void operator = (Argument argument)

  {

    callablePointer.reset(new CallableObject(argument));

  }

};

Перейти на страницу:

Похожие книги