Идиома для структуры мульти-обратного вызова с захватом переменных - PullRequest
0 голосов
/ 07 июня 2018

У меня есть функция, которая принимает объект и вызывает различные функции обратного вызова в качестве его вывода.Пример:

template<typename T>
void iterateInParallel(vector<int> const& a, vector<int> const& b, T && visitor)
{
    // sometimes invokes visitor.fromA(i)
    // sometimes invokes visitor.fromB(i)
    // sometimes invokes visitor.fromBoth(i, j)
}

(Не увлекайтесь точно тем, что делает функция, это пример и здесь не актуально.)

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

int sum = 0;
int numElems = 0;
someFunctionTakingACallback(args, [&](int x) {sum += x; numElems++; });

Это много лучше, чем старыйфункторный подход, в котором я бы определил класс для цели, которая в своих конструкторах приняла ссылки на sum и numElems.Как и в случае с функторным подходом, он обычно не требует затрат времени выполнения, поскольку код для обратного вызова известен при создании экземпляра функции.

Но лямбда-это только одна функция, поэтому она не работает для метода iterateInParallelвыше;Я возвращаюсь к копированию ссылок.

Итак, то, что я ищу, - это средство приблизительного уровня удобства, производительности и идиоматичности, которое у меня есть от захвата по умолчанию в lambdas.возможность вызывать несколько типов обратных вызовов.

Опции, которые я рассмотрел:

  • Передача нескольких функций обратного вызова.Это не так уж плохо, и обычно это то, что я пошел.Но порядок параметров можно легко перепутать, и это действительно может привести к расширению списка аргументов.Это также менее самодокументированное, так как имена обратных вызовов нигде в пользовательском коде.
  • Переписывают внутреннюю функцию так, что она вызывает один обратный вызов, с параметрами, помогающими определить, что kind обратного вызова это.Уродливый, хакерский, и не тот поступок, который мог бы сделать респектабельный программист.
  • Передача структуры с целой связкой std::function s.У меня такое чувство, что я мог бы заставить это выглядеть хорошо , но у меня также есть ощущение, что это в конечном итоге приведет к выделению кучи и перенаправлению на вызов.
  • Что-то дурацкое с участием препроцессора,Небеса прощают.

1 Ответ

0 голосов
/ 07 июня 2018

Направляющие вычитания (C ++ 17) и обозначают инициализацию (c ++ 20) вместе, дают подходящее решение:

#include <iostream>

template<class F1, class F2>
struct Visitor
{
    F1 fromA;
    F2 fromB;
};
template<class F1, class F2>
Visitor(F1, F2) -> Visitor<F1, F2>;


template<class F1, class F2>
void f(Visitor<F1, F2> v)
{
    v.fromA(1);
    v.fromB("hello");
}


int main()
{
    f( Visitor{
        .fromA = [](int n){ std::cout << "A: " << n << "\n"; },
        .fromB = [](std::string_view v){ std::cout << "B: " << v << "\n"; }
    });
}

( демо )

...