В строке 6 определяется тип объекта, который мы запросили. В строке 7 объявляется мета-экземпляр объекта соответствующего типа. Мы говорим «мета-экземпляр», потому что реально объект не создается, но его характеристики используются компилятором для анализа. Конструкция declval необходима, чтобы не было ошибки в случае, если объект не имеет конструктора по умолчанию.
В строке 8 производится мета-вызов с передачей параметров. Мета-вызов здесь имеет тот же смысл, что и мета-экземпляр, т. е. в реальности вызов не производится, а используется для анализа. В строке 9 определяется тип значения, возвращаемого мета-вызовом.
В строке 11 проверяется, является ли тип возвращаемого значения void, и в этом случае вызывается распределяющая функция без возврата результатов (строка 12). В противном случае вызывается распределяющая функция, возвращающая результаты (строка 13).
Использование распределителя с условной компиляцией приведено в Листинг 80.
struct FOReturn
{
int operator() (int eventID) {return 10;}
};
struct FOVoid
{
void operator() (int eventID) { /*do something*/ }
};
struct SResult
{
unsigned int code;
const char* description;
};
SResult ExternalReturn(int eventID)
{
return SResult{ 1, "this is an error" };
}
void ExternalVoid(int eventID)
{
}
int main()
{
int eventID = 0;
FOReturn foRet;
FOVoid foVoid;
auto lambdaRet = [](int eventID) { return 0.0; };
auto lambdaVoid = [](int eventID) {};
using FunPtrRet = SResult(*)(int);
using LambdaTypeRet = decltype(lambdaRet);
using FunPtrVoid = void(*)(int);
using LambdaTypeVoid = decltype(lambdaVoid);
StaticDistributor
StaticDistributor
auto results = distributor1(eventID);
distributor2(eventID);
}
Как видим, в обоих случаях объявляется один и тот же распределитель, а из свойств объектов распределения будет генерироваться соответствующий перегруженный оператор.
5.6. Динамический набор получателей
5.6.1. Распределение в динамическом наборе
В предыдущих параграфах мы рассматривали статический набор получателей, когда типы и количество получателей определены на этапе компиляции и остаются неизменными. Теперь рассмотрим динамический набор, когда типы и количество получателей заранее неизвестны и изменяются в процессе выполнения программы. В какой-то степени реализация здесь получается проще: у нас не будет специализаций, рекурсий, выведения типов и прочей так называемой «шаблонной магии», все решается обычными методами классического программирования.
Итак, поскольку количество объектов заранее не определено, для их хранения необходим динамический контейнер. Однако он не может хранить объекты непосредственно, поскольку они могут иметь разные типы, а динамический контейнер работает с данными одного строго определенного типа. Выходом будет хранить универсальные аргументы, а уже в них сохранять объекты вызова. Структурная схема изображена на Рис. 24.
Рис. 24. Структурная схема распределителя для динамического набора получателей
Оптимальным решением будет реализация распределителя в виде класса, который, кроме выполнения распределения, будет поддерживать операции с контейнером. Конечно же, проектировать динамический контейнер и универсальный аргумент не нужно – в STL имеется все необходимое. Контейнер, в общем-то, можно использовать любой, а на роль универсального аргумента нет ничего лучше, чем std::function. Реализация приведена в Листинг 81.
template
template
class DynamicDistributor
{
public:
template
void addCallObject(CallObject object) // (3)
{
callObjects.push_back(object);
}