Подтвердить что ты не робот

Вариадическая функция, принимающая функторы/вызываемые объекты

Проблема

Я хочу сделать функцию, которая принимает произвольное число объектов-функторов или, в общем, просто вызываемые объекты (разных типов) и применяет их к внутренней структуре данных. Функция будет использоваться с различным числом функторов в разных точках моего кода.

Вместо того, чтобы делать другую версию, чтобы принять 1,2,3... и т.д. и дублировать код, я думал об использовании вариативных шаблонов.

Я нашел решение, которое я опубликую ниже в качестве ответа, поскольку я не мог найти что-либо в Google об этом конкретно, и другие могут найти его полезным. Но, пожалуйста, если у кого-то есть лучшие идеи, опубликуйте их. У меня есть чувство, что должен быть стандартный способ сделать это?

Наивная попытка

Я вроде знал, что это не сработает, но моя первая попытка была

#include <iostream>
using namespace std;

struct FunctorA {
    void operator() () {
        cout << "FunctorA" << endl;
    }
};

struct FunctorB {
    void operator() () {
        cout << "FunctorB" << endl;
    }
};

template<typename... Fs>
void apply_functors(Fs... fs) {
     fs()...;   // will not work - can only expand packs in certain situations (e.g. as funciton 
}

int main(void) {
    apply_functors(FunctorA(),FunctorB());
    apply_functors(FunctorA());
    apply_functors([]()->void{ cout << "Lambda" << endl; });
    return 0;
}

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

Попытка 2

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

template<typename... Fs>
void _dummyF_impl(Fs... fs){}

template<typename... Fs>
void apply_functors(Fs... fs) {
    _dummyF_impl( fs()... );    // only works if all functors return a value (else we get "invalid use of 'void'")
}

Однако это не сработает, потому что функторы возвращают void из их operator(), и мы не можем передавать void в качестве параметров для функций.

4b9b3361

Ответ 1

Расширение массива аргументов внутри расширенного инициализатора имеет дополнительное преимущество гарантированной оценки слева направо (что не относится к списку аргументов функции). Вы также должны рассмотреть возможность использования совершенной пересылки здесь, почему.

#include <initializer_list>  //
#include <utility>  // std::forward

template<typename... Fs>
void apply_functors(Fs&&... fs)
{
     auto list = { (std::forward<Fs>(fs)(), 0)... };
     //   ^^^^ deduced as std::initializer_list
}

То же самое в принципе, но на этот раз без необходимости включения <initializer_list> является конструктором конструкторов Variadic, который вы также можете вызвать с помощью принудительного инициализатора:

struct dummy {
    template<typename... Fs>
    dummy(Fs&&... fs) { }
};

template<typename... Fs>
void apply_functors(Fs&&... fs)
{
     dummy { (std::forward<Fs>(fs)(), 0)... }; // evaluated left-to-right, too
}

Ответ 2

Я бы сделал этот путь

template<typename... Fs>
void apply_functors(Fs... fs) {
    int i[] = { ((void) fs(), 0)... };
    (void) i; // prevents a warning about i not being used.
}

Одна из ситуаций, в которой происходит расширение пакета параметров, - это внутренние инициализаторы. Выше я использовал его в инициализации массива.

Шаблон (fs(), 0)... расширяется до (f1(), 0), (f2(), 0), ..., (fn(), 0), где f1,..., fn являются предоставленными вызываемыми объектами. Обратите внимание, что вычисление выражения (f1(), 0) вызывает f1(), игнорирует его возвращаемое значение, а результатом выражения является 0 (a int).

Более сложный шаблон ((void) fs(), 0)... требуется, чтобы предотвратить случай угла, а именно, когда fs() возвращает тип, для которого перегружен оператор запятой.

Дополнительным преимуществом решения, использующего фиктивную функцию, является то, что порядок, в котором вычисляются аргументы функции, не задается стандартом, а в инициализации массива (слева направо). Например, обратите внимание, что вызов apply_functors(FunctorA(),FunctorB()); в Dan решение выводит FunctorB до FunctorA тогда как с этим решением здесь выходной сигнал FunctorA, за которым следует FunctorB.

Обновление:. После чтения jrok я осознал необходимость для идеальной пересылки. Таким образом, мое обновленное решение будет

template<typename... Fs>
void apply_functors(Fs&&... fs) {
    int i[] = { ((void) std::forward<Fs>(fs)(), 0)... };
    (void) i; // prevents a warning about i not being used.
}

Ответ 3

Мое решение

Решение, которое я нашел, было оператором запятой. У меня никогда не было причин использовать это раньше, но он прекрасно подходит здесь.

Оператор запятой может использоваться для оценки отдельных операторов, но затем "возвращает" значение последнего выражения, поэтому a,b,c оценивается как c.

Таким образом, мы можем использовать это для оценки наших функторов, игнорировать возвращаемое значение void, а затем вернуть то, что передается в _dummyF_impl:

template<typename... Fs>
void apply_functors(Fs... fs) {
   _dummyF_impl( (fs(),0)... );
}

Это компилируется под g++ 4.7.3, а при запуске вывод выполняется так, как мы ожидали:

FunctorB
FunctorA
FunctorA
Lambda