Функция с двумя переменными параметрами

Я намерен реализовать шаблонную функцию, которая будет принимать две функции и список их параметров, а затем создавать две оболочки. Я уже реализовал аналогичное решение, чтобы принять две функции и создать оболочку, используя std::bind(), но используя жестко запрограммированные параметры. Решение примерно такое:

template <typename T1, typename T2, typename T3, typename T4>
myWrapper (T2 func1, T4 func2) {
  std::function<T1>ff = std::bind(func1, 1, placeholders::_1);
  std::function<T3>ff = std::bind(func1, 110, 20, "something");
}

Как вы видите, параметры std::bind() жестко закодированы в обоих случаях, и я хотел бы прочитать их через функцию myWrapper(). Есть ли способ прочитать два вариативных шаблона (список переменных произвольной длины и типа) и отправить их на два вызова std::bind(), которые у меня есть в приведенном выше коде?

Спасибо,

Дэн


person Dan    schedule 19.10.2017    source источник
comment
Не могли бы вы привести пример того, как вы хотите, чтобы функция вызывалась?   -  person Quentin    schedule 19.10.2017
comment
Ну, на самом деле я не знаю! Вот в чем вопрос! :) Обычно я хочу что-то вроде этого: template ‹typename... Args› void fun2(variadicStruct‹Args...› args) { autowrappedFunc1 = std::bind(originalFunc1, args.args1); autowrappedFunc2 = std::bind(originalFunc2, args.args2); } fun2({(это, std::placeholders::_1), (это, 1, 2)}); где this, std::placeholders::_1 будет передано в оболочку первой функции, а this, 1, 2 будет передано в оболочку второй функции. Это мое намерение.   -  person Dan    schedule 19.10.2017
comment
Не могли бы вы привести пример того, что вы имеете в виду, когда я могу без проблем прочитать один вариативный входной параметр?   -  person max66    schedule 19.10.2017
comment
Да, конечно! У вас всегда может быть что-то вроде этого: template ‹typename... Args› void fun2(int arg1, int arg2, Args ..args) { // Сделайте что-нибудь с аргументами. } потому что компилятор может успешно вывести два первых параметра, а затем вывести остальные как переменные. Но наличие двух переменных сделает список параметров невыводимым. Это проблема!   -  person Dan    schedule 19.10.2017
comment
Я не понимаю, что вы имеете в виду в своем примере кода с variadicStruct. Вы определяете его с помощью одного параметра шаблона, T, но в fun2() вы используете его для получения вариативного списка типов шаблонов (Args...). Вы имеете в виду что-то типа template <typename ... Args> struct variadicStruct { std::tuple<Args...> args1; std::tuple<Args...> args2; }; ?   -  person max66    schedule 19.10.2017
comment
У вас есть два списка одинаковых типов (одинаковые типы, одинаковая длина) или два разных списка (разные типы и разная длина)?   -  person max66    schedule 19.10.2017
comment
max66: Я думаю, что фрагмент кода будет работать в любом случае, потому что я использую шаблон T в структуре, которую позже можно определить как вариативную. Но в любом случае, отвечая на ваш вопрос, да, мне нужны два списка параметров, длину которых и тип данных в них я никогда не узнаю. Типы данных в каждом списке не совпадают, так как это будет список параметров функции.   -  person Dan    schedule 19.10.2017
comment
Извините, но... ваш вопрос слишком расплывчатый, если вы представите такой пример, который позже можно будет определить как вариативный. Теперь мне нужно понять, как определяется variadicStruct, как вы хотите вызывать fun2() и что вы хотите делать с этими двумя списками параметров. Теперь у меня в голове только большая путаница, и я подозреваю, что я не одинок. Предлагаю вам полностью переписать этот вопрос (а лучше удалить и написать другой) давая понятное описание вашей проблемы   -  person max66    schedule 19.10.2017
comment
Изменил вопрос! Надеюсь, теперь это более понятно.   -  person Dan    schedule 19.10.2017


Ответы (3)


Не уверен, что понимаю, чего вы хотите, но я полагаю, вам нужно использовать std::tuple (или что-то подобное) для разделения двух последовательностей аргументов.

Ниже приведен полный рабочий пример того, что я имею в виду, в соответствии с тем, что я понимаю, чего вы хотите.

#include <tuple>
#include <string>
#include <utility>
#include <iostream>
#include <functional>

template <typename T1, typename T2, typename T3, typename T4,
          typename ... Ts1, typename ... Ts2,
          std::size_t ... Is1, std::size_t ... Is2>
auto myWrapperH (T3 func1, T4 func2, std::tuple<Ts1...> const & tp1,
                 std::tuple<Ts2...> const & tp2,
                 std::index_sequence<Is1...> const &,
                 std::index_sequence<Is2...> const &)
 {
   T1 f1 = std::bind(func1, std::get<Is1>(tp1)...);
   T2 f2 = std::bind(func2, std::get<Is2>(tp2)...);

   return std::make_pair(f1, f2);
 }

template <typename T1, typename T2, typename T3, typename T4,
          typename ... Ts1, typename ... Ts2>
auto myWrapper (T3 func1, T4 func2, std::tuple<Ts1...> const & tp1,
                std::tuple<Ts2...> const & tp2)
 { return myWrapperH<T1, T2>(func1, func2, tp1, tp2,
                             std::make_index_sequence<sizeof...(Ts1)>{},
                             std::make_index_sequence<sizeof...(Ts2)>{}); }

int foo (int a, int b)
 { return a+b; }

std::size_t bar (int a, int b, std::string const & str)
 { return str.size() + a + b; }

int main ()
 {
   using fType1 = std::function<int(int)>;
   using fType2 = std::function<long()>;

   auto mwr = myWrapper<fType1, fType2>(&foo, &bar,
                 std::make_tuple(1, std::placeholders::_1),
                 std::make_tuple(110, 20, std::string{"something"}));

   std::cout << mwr.first(5) << std::endl; // print   6
   std::cout << mwr.second() << std::endl; // print 139
 }

К сожалению, это код C++14 (тип возврата auto; std::index_sequence и std::make_index_sequence), но его легко адаптировать в C++11.

-- ИЗМЕНИТЬ--

Как указал Банан (спасибо!), нет необходимости явно указывать тип возвращаемых функций (T1, T2).

Используя тип возвращаемого значения auto, пример можно упростить следующим образом.

#include <tuple>
#include <string>
#include <utility>
#include <iostream>
#include <functional>

template <typename F1, typename F2, typename ... Ts1, typename ... Ts2,
          std::size_t ... Is1, std::size_t ... Is2>
auto myWrapperH (F1 func1, F2 func2, std::tuple<Ts1...> const & tp1,
                 std::tuple<Ts2...> const & tp2,
                 std::index_sequence<Is1...> const &,
                 std::index_sequence<Is2...> const &)
 { return std::make_pair(std::bind(func1, std::get<Is1>(tp1)...),
                         std::bind(func2, std::get<Is2>(tp2)...)); }

template <typename F1, typename F2, typename ... Ts1, typename ... Ts2>
auto myWrapper (F1 func1, F2 func2, std::tuple<Ts1...> const & tp1,
                std::tuple<Ts2...> const & tp2)
 { return myWrapperH(func1, func2, tp1, tp2,
                     std::make_index_sequence<sizeof...(Ts1)>{},
                     std::make_index_sequence<sizeof...(Ts2)>{}); }

int foo (int a, int b)
 { return a+b; }

std::size_t bar (int a, int b, std::string const & str)
 { return str.size() + a + b; }

int main ()
 {
   auto mwr = myWrapper(&foo, &bar,
                 std::make_tuple(1, std::placeholders::_1),
                 std::make_tuple(110, 20, std::string{"something"}));

   std::cout << mwr.first(5) << std::endl; // print   6
   std::cout << mwr.second() << std::endl; // print 139
 }
person max66    schedule 19.10.2017
comment
Хорошее решение, я сам работал над чем-то подобным. Можете ли вы устранить необходимость явного предоставления myWrapper с fType1 и fType2? Кажется, что-то, что может быть выведено компилятором :) - person Banan; 19.10.2017
comment
@Банан - спасибо; нет необходимости использовать возвращаемое значение auto; но я не знаю, хорошая ли это идея, потому что я не знаю, почему ОП требует, чтобы это было явно. Но, может быть, я явно это... - person max66; 19.10.2017

Что касается вашего представления о variadicStruct, вы можете взглянуть на std::tuple.

template<class... Ts, class... Us>
void do_stuff(const std::tuple<Ts...>&, const std::tuple<Us...>&) {
  std::cout << "two variadic packs with " << sizeof...(Ts)
        << " and " << sizeof...(Us) << " elements." << std::endl;
}

Называется так:

do_stuff(std::make_tuple(4.7, 'x', 1.0, 4, 8l), std::make_tuple("foo", 1));
person Julius    schedule 19.10.2017
comment
Было бы очень хорошо использовать кортежи. Но проблема заключалась в распаковке данных из кортежей. Теперь я вижу, что вы легко распаковали их, прочитав непосредственно Ц и Мы. Я попробую. Надеюсь это работает. Спасибо, в любом случае. - person Dan; 19.10.2017
comment
@Dan std::index_sequence_for<Ts...> очень помогает в распаковке кортежей. en.cppreference.com/w/cpp/utility/integer_sequence - person aschepler; 19.10.2017
comment
Спасибо! Я надеюсь, что это не часть C++14/17, потому что пока мы весьма ограничены C++11. - person Dan; 19.10.2017
comment
@Dan: std::tuple был представлен в C++11 и должен работать сразу. Однако std::index_sequence и помощники (как предложил @aschepler) - это С++ 14. Последний может быть заменен некоторым эквивалентным помощником из другой библиотеки/пространства имен. Обратите внимание, что хорошие реализации std::make_index_sequence и т. д. должны стремиться к глубине логарифмической рекурсии и, кроме того, оптимизировать преимущества мемоизации. - person Julius; 19.10.2017

Вы можете использовать std::make_tuple и std::apply, например:

template <typename R, typename Func1, typename Args1>
void wrapper (Func1 f1, Args1 a1)
{
    std::function<R> wrapped1 = [f1, a1]{ return std::apply(f1, a1); };
}

// ...

auto deg_to_rad = [](int x){ return x / 180.f * 3.1415f; };
wrapper<float>(deg_to_rad, std::make_tuple(45));

std::apply требуется поддержка C++17. Однако связанная страница cppreference содержит возможную реализацию, которая работает на C+. +11.

person lisyarus    schedule 19.10.2017