C ++ 11 variadic std :: function параметр - PullRequest
18 голосов
/ 11 февраля 2012

Функция с именем test принимает в качестве параметра std :: function <>.

template<typename R, typename ...A>
void test(std::function<R(A...)> f)
{
    // ...
}

Но, если я сделаю следующее:

void foo(int n) { /* ... */ }

// ...

test(foo);

Компилятор (gcc 4.6.1) говорит: no matching function for call to test(void (&)(int)).

Чтобы последняя строка test(foo) компилировалась и работала правильно, как я могу изменить функцию test()? В test() функции мне нужно f с типом std :: function <>.

Я имею в виду, есть ли какие-то хитрости шаблона, позволяющие компилятору определять сигнатуру функции (например, foo) и автоматически преобразовывать ее в std::function<void(int)>?

EDIT

Я хочу сделать эту работу также для лямбд (как заявленных, так и не имеющих состояния).

Ответы [ 4 ]

11 голосов
/ 11 февраля 2012

Похоже, вы хотите использовать перегрузку

template<typename R, typename ...A>
void test(R f(A...))
{
    test(std::function<R(A...)>(f));
}

Эта простая реализация примет большинство, если не все функции, которые вы попытаетесь передать. Экзотические функции будут отклонены (например, void(int...)). Больше работы даст вам больше универсальности.

7 голосов
/ 12 февраля 2012

std::function реализует интерфейс Callable, то есть он выглядит как функция, но это не значит, что вам нужно, чтобы вызываемые объекты были std::function s.

template< typename F > // accept any type
void test(F const &f) {
    typedef std::result_of< F( args ) >::type R; // inspect with traits queries
}

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

Если вам действительно нужен std::function, например, для перенацеливания переменной или чего-то такого безумного, и вы знаете, что ввод является необработанным указателем на функцию, вы можете разложить тип указателя на необработанную функцию и преобразовать его в std::function.

template< typename R, typename ... A >
void test( R (*f)( A ... ) ) {
    std::function< R( A ... ) > internal( f );
}

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

Что касается лямбд с состоянием, я не знаю, как справиться с этим делом. Они не разлагаются на указатели функций, и, насколько я знаю, типы аргументов не могут быть запрошены или выведены. Эта информация необходима для создания экземпляра std::function, в лучшую или в худшую сторону.

4 голосов
/ 12 февраля 2012

Обычно не рекомендуется принимать std::function по значению, если только вы не находитесь в режиме «двоичного разграничения» (например, динамическая библиотека, «непрозрачный» API), поскольку, как вы только что засвидетельствовали, они играют хаос с перегрузкой. Когда функция действительно принимает std::function по значению, то вызывающему часто приходится создавать объект, чтобы избежать проблем с перегрузкой (если функция вообще перегружена).

Поскольку вы уже написали шаблон, вполне вероятно, что вы не используете std::function (в качестве типа параметра) для преимуществ стирания типа. Если вы хотите проверить произвольные функторы, то для этого вам понадобятся некоторые особенности. Например. Boost.FunctionTypes имеет такие черты, как result_type и parameter_types. Минимальный, функциональный пример:

#include <functional>

#include <boost/function_types/result_type.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/function_type.hpp>

template<typename Functor>
void test(Functor functor) // accept arbitrary functor!
{
    namespace ft = boost::function_types;

    typedef typename ft::result_type<Functor>::type result_type;
    typedef ft::parameter_types<Functor> parameter_types;
    typedef typename boost::mpl::push_front<
        parameter_types
        , result_type
    >::type sequence_type;
    // sequence_type is now a Boost.MPL sequence in the style of
    // mpl::vector<int, double, long> if the signature of the
    // analyzed functor were int(double, long)

    // We now build a function type out of the MPL sequence
    typedef typename ft::function_type<sequence_type>::type function_type;

    std::function<function_type> function = std::move(functor);
}

В качестве последнего замечания, я не рекомендую интроспективные функторы (т. Е. Подталкивать к их типу результата и типу аргумента) в общем случае, поскольку они просто не работают для полиморфных функторов. Рассмотрим несколько перегруженных operator(): тогда нет «канонического» типа результата или типа аргумента. В C ++ 11 лучше «охотно» принимать любой тип функтора или ограничивать их, используя такие методы, как SFINAE или static_assert, в зависимости от потребностей, а затем (когда параметры доступны), чтобы использовать std::result_of для проверки тип результата для заданного набора аргументов . Случай, когда желательно ограничить заранее, состоит в том, когда целью является сохранение функторов, например, в. контейнер std::function<Sig>.

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

3 голосов
/ 31 мая 2014

Это старый, и я не могу найти много по той же теме, поэтому я решил пойти дальше и добавить примечание.

Скомпилировано в GCC 4.8.2,следующие работы:

template<typename R, typename... A>
R test(const std::function<R(A...)>& func)
{
    // ...
}

Однако вы не можете просто вызвать его, передав указатели, лямбды и т. д. Однако оба следующих примера работают с ним:

test(std::function<void(int, float, std::string)>(
        [](int i, float f, std::string s)
        {
            std::cout << i << " " << f << " " << s << std::endl;
        }));

Также:

void test2(int i, float f, std::string s)
{
    std::cout << i << " " << f << " " << s << std::endl;
}

// In a function somewhere:
test(std::function<void(int, float, std::string)>(&test2));

Недостатки этого должны быть очевидны: вам нужно явно объявить для них функцию std ::, что может показаться немного уродливым.

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

Пример кода, включающего элемент кортежа, если вы хотите поиграть с ним: http://ideone.com/33mqZA

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...