Упрощение использования std :: function, хранящегося в члене класса std :: vector

Я написал этот базовый класс для хранения std::function<T> в std::vector<T>, и у меня есть два бесплатных шаблона функций foo() и bar(), которые оба возвращают void и принимают std::vector<T> в качестве параметра. В настоящее время они делают то же самое для простоты; но предположим, что для справки в будущем они будут выполнять другие вычисления или задачи. Пока это то, что я придумал:

#include <vector>
#include <functional
#include <iostream>
#include <exception>

template<typename T>
class MyClass {
private:
    std::vector<std::function<void(std::vector<T>)>> myFuncs_;    
public:
    MyClass() = default;

    void addFunc( std::function<void(std::vector<T>)> func ) {
        myFuncs_.push_back(func);
    }

    std::function<void(std::vector<T>)> caller(unsigned idx) {
        return myFuncs_.at(idx);
    }
};

template<typename T>
void foo(std::vector<T> data) {
    std::cout << "foo() called:\n";

    for (auto& d : data) 
        std::cout << d << " ";
    std::cout << '\n';
}

template<typename T>
void bar(std::vector<T> data) {
    std::cout << "bar() called:\n";

    for (auto& d : data)
        std::cout << d << " ";
    std::cout << '\n';
} 

int main() {
    try {
        MyClass<int> myClass;
        std::vector<int> a{ 1,3,5,7,9 };
        std::vector<int> b{ 2,4,6,8,10 };

        std::function<void(std::vector<int>)> funcA = std::bind(foo<int>, a);
        std::function<void(std::vector<int>)> funcB = std::bind(bar<int>, b);
        myClass.addFunc( funcA );
        myClass.addFunc( funcB );

        myClass.caller(0)(a);
        myClass.caller(1)(b);       

    } catch( std::runtime_error& e ) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;    
}  

И этого достаточно, чтобы вывести:

foo() called:
1 3 5 7 9
bar() called:
2 4 6 8 10

Вот что я хотел бы знать: есть ли способ (-а) упростить этот код; некоторые из синтаксиса выглядят избыточными, например: в основной функции после того, как я создал экземпляр шаблона моего класса и создал std::vector со значениями, я затем создаю экземпляр std::function<T>, используя std::bind с функцией, которую я хочу затем добавить в свой вектор класса. Затем, привязав их и добавив в контейнер моего класса, я вызываю функцию класса для индексации функции, которую я хочу вызвать, с помощью std::functionsoperator(). Однако функция ожидает std::vector<T>, поэтому похоже, что я передаю этот vector<T> несколько раз, как вы можете видеть из этой части моего кода выше.

// std::vector<T> being passed to std::bind
std::function<void(std::vector<int>)> funcA = std::bind(foo<int>,a);
myClass.addFunc( funcA );
myClass.caller(0)(a);  // Again std::vector<T> being passed but to `std::function`'s operator because the stored function requires this parameter. 

Можно ли это упростить или это просто семантика того, как работают std::function и std::bind? Если это можно упростить, будет ли это сделано внутри самого класса, чтобы упростить задачу для пользователя, или это будет сделано со стороны пользователя?

Использование вывода типа компилятора с auto - хорошее начало для упрощения. Как и использование псевдонимов типов в классе для часто используемых типов (например, типа std::function).

Some programmer dude 29.12.2018 10:06

И вместо функции-получателя, которая возвращает функцию, возможно, вместо нее будет функция «вызова» (или, возможно, даже оператор вызова)?

Some programmer dude 29.12.2018 10:08

@Someprogrammerdude Я думал об этих строках, но я пока не очень эффективно использую std::function и std::bind, а синтаксис загромождает мой мыслительный процесс; ха-ха .... С другой стороны, уже немного поздно, я мог бы сделать перерыв и поработать над этим завтра.

Francis Cugler 29.12.2018 10:09

@Someprogrammerdude пример type-aliases и функции call были бы признательны.

Francis Cugler 29.12.2018 10:11

@FrancisCugler Вместо std::bind() вы должны использовать захватывающие лямбда-функции.

πάντα ῥεῖ 29.12.2018 10:12

Здесь вам не нужен bind: foo<int> уже имеет соответствующий тип ... Это станет более очевидным, если вы вместо этого используете лямбда; это эквивалентно тому, что произвел бы bind: funcA = [&]() { foo<int>(a); } - обратите внимание на несовпадающую подпись ...

Aconcagua 29.12.2018 10:20

@Aconcagua Я пробовал лямбду; но я думаю, что это единственное, чего мне не хватало, - reference в пункте захвата.

Francis Cugler 29.12.2018 10:21

@FrancisCugler Но вам это не нужно, ни привязка, ни лямбда: Просто имейте: std::function<void(std::vector<int>) f = foo<int>;: f хочет, чтобы что-то возвращало void и принимало вектор, foo делает это.

Aconcagua 29.12.2018 10:24

@Aconcagua хм, MSVC вызывает у меня проблемы с попыткой назначить лямбду для std::function ...

Francis Cugler 29.12.2018 10:24

@FrancisCugler Из-за несоответствия типа см. Предыдущие комментарии ...

Aconcagua 29.12.2018 10:25

@Aconcagua Я думаю, это то, чего мне не хватало. После замены на ... = foo<int>; и ... = bar<int>; я думаю, что начинаю немного лучше понимать std::function. Я объявляю funcA и funcB как std::function<T>, но при их назначении вы можете назначить им адрес функции; таким образом, pointer to a function.

Francis Cugler 29.12.2018 10:28

@FrancisCugler ... или лямбда, или объект-функтор - пока подпись совпадает (лямбда) / предоставляется соответствующий operator() (функтор). std::function - это универсальный контейнер.

Aconcagua 29.12.2018 10:32

@Aconcagua На самом деле MSVC здесь не ошибается, потому что функция std::binded будет игнорировать любые избыточные аргументы вместо ошибки компиляции. Это ожидаемо. @FrancisCugler Ваш код не работает, он работает случайно. a в myClass.caller(0)(a); вообще не действует. Можно попробовать заменить на myClass.caller(0)(b);, вывод должен быть точно таким же.

llllllllll 29.12.2018 10:34

@liliscent Здесь я подумал, что .caller(0) даст индекс vector, используя его функцию at(), а затем, поскольку это извлекает std::function, который хранится с индексом 0 или 1, который использует (a) или (b) после того, как он затем будет использовать std::functionoperator() позволяя call указать указатель на функцию, а также передать ему необходимые параметры ... Или мне что-то не хватает?

Francis Cugler 29.12.2018 10:46

@FrancisCugler У вас серьезное недоразумение по поводу std::bind, это нелегко объяснить, потому что я не знаю, почему вы неправильно это поняли. Я предлагаю вам внимательно прочитать Примеры в документации. en.cppreference.com/w/cpp/utility/functional/bind

llllllllll 29.12.2018 10:52

@FrancisCugler Повторная попытка с лямбда: [&](std::vector<int> other) { foo<int>(a); } Важная вещь: bind(foo<int>, a) вызывает foo с a вместо other.

Aconcagua 29.12.2018 10:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
16
185
1

Ответы 1

Вы неправильно используете std::bind, например, в

std::function<void(std::vector<int>)> funcA = std::bind(foo<int>, a);

Вы уже связали аргумент a с foo<int>, что означает, что любой параметр, который вы передадите в funcA, не вступит в силу, приведенное выше эквивалентно

std::function<void()> funcA = std::bind(foo<int>, a);

Для простоты кода, если вы хотите придерживаться std::bind, вы можете просто иметь оператора вызова на MyClass, который вызывает все зарегистрированные std::function:

template<typename T>
class MyClass {
private:
    std::vector<std::function<void()>> myFuncs_;
public:
    MyClass() = default;

    template <class... F, std::enable_if_t<(sizeof...(F) > 0), bool> = true>
    void addFunc(F&&... func) {
        ((myFuncs_.push_back(std::forward<F>(func))), ...);
    }

    void operator()() const {
        for (auto& fn : myFuncs_) {
            fn();
        }
    }
};


auto funcA = std::bind(foo<int>, a);
auto funcB = std::bind(bar<int>, b);
myClass.addFunc(funcA, funcB);
myClass();

используя ту же идею, вы можете в любой момент переключиться на лямбды.

Другие вопросы по теме