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

Пример использования распределителя приведен в Листинг 78.

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

struct FO

{

  int operator() (int eventID) { return 10; }

  int callbackHandler(int eventID) { return 0; }

};

struct SResult

{

  unsigned int code;

  const char* description;

};

SResult ExternalHandler(int eventID)

{

  return SResult{ 1, "this is an error" };

}

int main()

{

  FO fo;

  int eventID = 0;

  auto lambda = [](int eventID) { return 0.0; };

  auto callbackToMethod = std::bind(&FO::callbackHandler, fo, std::placeholders::_1);

  StaticDistributorReturn distributor(ExternalHandler, fo, callbackToMethod, lambda);  // (1)

  auto [resExtHandler, resFoOperator, resFoMethod, resLambda] = distributor(eventID);  // (2)

}

В строке 1 объявляется распределитель, в конструктор передаются объекты вызова. Через перегруженный оператор 2 производятся вызовы хранимых объектов, результаты возвращаются с помощью структурных привязок.

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

5.5.3. Параметризация возвращаемого значения

Итак, у нас имеется отдельная реализация распределителя для случая, когда результаты вызовов не требуются, и отдельная реализация для случая, когда необходимо получать возвращаемые значения. Обе реализации одинаковы, за исключением перегруженного оператора. Как сделать общую реализацию для обеих случаев? Разместить два перегруженных оператора в одном классе не получится, потому что они различаются только типом возвращаемого значения. Можно предложить следующее решение: ввести в шаблон дополнительный параметр, который указывает, нужно ли возвращать результаты выполнения вызовов, и в зависимости от этого по-разному формировать перегруженный оператор с помощью условной компиляции. Реализация приведена в Листинг 79.

Листинг 79. Условная компиляция в зависимости от типа возвращаемого значения

template<typename… CallObjects>  // (1)

class StaticDistributor

{

public:

  StaticDistributor(CallObjects… objects) : callObjects(objects…) {}  // (2)

  auto& tuple() { return callObjects; }  // (3)

  template<typename… CallData> 

  auto operator() (CallData… callData)  // (4)

  {

#define callObject std::get<0>(callObjects)          // (5)

#define callObjType decltype(callObject)             // (6)

#define callObjInstance std::declval<callObjType>()  // (7)

#define testCall callObjInstance(callData…)        // (8)

#define retType decltype(testCall)                   // (9)

  //if constexpr (std::is_same_v<void, decltype(std::declval<decltype(std::get<0>(callObjects))>()(callData…))>)  // (10)

  if constexpr (std::is_same_v<void, retType>)         // (11)

    return Distribute2(callObjects, callData…);      // (12)

  else                              

    return DistributeReturn(callObjects, callData…);  // (13)

  }

private:

  std::tuple<CallObjects…> callObjects;

};

В строках 1 – 4 код идентичен реализации распределителя в предыдущих случаях (Листинг 75 п. 5.5.1, Листинг 77 п. 5.5.2). Интерес представляет реализация перегруженного оператора (строка 4).

Макросы в строках 5 – 9 предназначены только для облегчения понимания кода, без них конструкция получается запутанной (строка 10).

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

В строке 6 определяется тип объекта, который мы запросили. В строке 7 объявляется мета-экземпляр объекта соответствующего типа. Мы говорим «мета-экземпляр», потому что реально объект не создается, но его характеристики используются компилятором для анализа. Конструкция declval необходима, чтобы не было ошибки в случае, если объект не имеет конструктора по умолчанию.

В строке 8 производится мета-вызов с передачей параметров.  Мета-вызов здесь имеет тот же смысл, что и мета-экземпляр, т. е. в реальности вызов не производится, а используется для анализа. В строке 9 определяется тип значения, возвращаемого мета-вызовом.