"распаковка" кортежа для вызова соответствующего указателя функции
Я пытаюсь хранить в std::tuple различное число значений, которые позже будут использоваться в качестве аргументов для вызова указателя функции, который соответствует сохраненным типам.
Я создал упрощенный пример, показывающий проблему, которую я изо всех сил пытаюсь решить:
#include <iostream>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template <typename ...Args>
struct save_it_for_later {
std::tuple<Args...> params;
void (*func)(Args...);
void delayed_dispatch() {
// How can I "unpack" params to call func?
func(std::get<0>(params), std::get<1>(params), std::get<2>(params));
// But I *really* don't want to write 20 versions of dispatch so I'd rather
// write something like:
func(params...); // Not legal
}
};
int main() {
int a=666;
double b = -1.234;
void *c = NULL;
save_it_for_later<int,double,void*> saved = {
std::tuple<int,double,void*>(a,b,c), f};
saved.delayed_dispatch();
}
обычно для проблем, связанных с std::tuple или вариативные шаблоны я бы написал другой шаблон, как template <typename Head, typename ...Tail> рекурсивно оценивать все типы один за другим, но я не вижу способа сделать это для отправка вызова функции.
реальная мотивация для этого несколько сложнее, и это в основном просто учебное упражнение в любом случае. Вы можете предположить, что я передал кортеж по контракту из другого интерфейса, поэтому его нельзя изменить, но желание распаковать его в вызов функции принадлежит мне. Это исключает использование std::bind как дешевый способ обойти основную проблему.
что такое чистый способ отправки вызова с помощью std::tuple, или альтернативный лучший способ достижения того же чистого результата хранения / пересылки некоторых значений и указателя функции до произвольной будущей точки?
8 ответов:
вам нужно построить пакет параметров чисел и распаковать их
template<int ...> struct seq { }; template<int N, int ...S> struct gens : gens<N-1, N-1, S...> { }; template<int ...S> struct gens<0, S...> { typedef seq<S...> type; }; // ... void delayed_dispatch() { callFunc(typename gens<sizeof...(Args)>::type()); } template<int ...S> void callFunc(seq<S...>) { func(std::get<S>(params) ...); } // ...
Это полная компилируемая версия решение Ингер Йоханне к вопросу awoodland, в надежде, что это может быть полезно кому-то. Это было протестировано со снимком g++ 4.7 на Debian squeeze.
################### johannes.cc ################### #include <tuple> #include <iostream> using std::cout; using std::endl; template<int ...> struct seq {}; template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {}; template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; }; double foo(int x, float y, double z) { return x + y + z; } template <typename ...Args> struct save_it_for_later { std::tuple<Args...> params; double (*func)(Args...); double delayed_dispatch() { return callFunc(typename gens<sizeof...(Args)>::type()); } template<int ...S> double callFunc(seq<S...>) { return func(std::get<S>(params) ...); } }; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wunused-but-set-variable" int main(void) { gens<10> g; gens<10>::type s; std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5); save_it_for_later<int,float, double> saved = {t, foo}; cout << saved.delayed_dispatch() << endl; } #pragma GCC diagnostic popможно использовать следующий файл SConstruct
##################### SConstruct ##################### #!/usr/bin/python env = Environment(CXX="g++-4.7", CXXFLAGS="-Wall -Werror -g -O3 -std=c++11") env.Program(target="johannes", source=["johannes.cc"])на моей машине, это дает
g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc g++-4.7 -o johannes johannes.o
вот решение C++14.
template <typename ...Args> struct save_it_for_later { std::tuple<Args...> params; void (*func)(Args...); template<std::size_t ...I> void call_func(std::index_sequence<I...>) { func(std::get<I>(params)...); } void delayed_dispatch() { call_func(std::index_sequence_for<Args...>{}); } };Это все еще нуждается в одной вспомогательной функции (
call_func). Поскольку это распространенная идиома, возможно, стандарт должен поддерживать ее непосредственно какstd::callС возможной реализацией// helper class template<typename R, template<typename...> class Params, typename... Args, std::size_t... I> R call_helper(std::function<R(Args...)> const&func, Params<Args...> const¶ms, std::index_sequence<I...>) { return func(std::get<I>(params)...); } // "return func(params...)" template<typename R, template<typename...> class Params, typename... Args> R call(std::function<R(Args...)> const&func, Params<Args...> const¶ms) { return call_helper(func,params,std::index_sequence_for<Args...>{}); }тогда наша задержанная отправка становится
template <typename ...Args> struct save_it_for_later { std::tuple<Args...> params; std::function<void(Args...)> func; void delayed_dispatch() { std::call(func,params); } };
решение C++17 просто использовать
std::apply:auto f = [](int a, double b, std::string c) { std::cout<<a<<" "<<b<<" "<<c<< std::endl; }; auto params = std::make_tuple(1,2.0,"Hello"); std::apply(f, params);просто почувствовал, что должен быть заявлен один раз в ответе в этой теме (после того, как он уже появился в одном из комментариев).
основное решение C++14 все еще отсутствует в этом потоке. EDIT: нет, это на самом деле есть в ответе Уолтера.
эта функция задается:
void f(int a, double b, void* c) { std::cout << a << ":" << b << ":" << c << std::endl; }вызовите его со следующим фрагмент:
template<typename Function, typename Tuple, size_t ... I> auto call(Function f, Tuple t, std::index_sequence<I ...>) { return f(std::get<I>(t) ...); } template<typename Function, typename Tuple> auto call(Function f, Tuple t) { static constexpr auto size = std::tuple_size<Tuple>::value; return call(f, t, std::make_index_sequence<size>{}); }пример:
int main() { std::tuple<int, double, int*> t; //or std::array<int, 3> t; //or std::pair<int, double> t; call(f, t); }
Это немного сложно достичь (хотя это возможно). Я советую вам использовать библиотеку, где это уже реализовано, а именно импульс.Fusion (the вызов
c++14 решение. Во-первых, некоторые утилиты шаблонные:
template<std::size_t...Is> auto index_over(std::index_sequence<Is...>){ return [](auto&&f)->decltype(auto){ return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... ); }; } template<std::size_t N> auto index_upto(std::integral_constant<std::size_t, N> ={}){ return index_over( std::make_index_sequence<N>{} ); }они позволяют вызывать лямбду с серией целых чисел времени компиляции.
void delayed_dispatch() { auto indexer = index_upto<sizeof...(Args)>(); indexer([&](auto...Is){ func(std::get<Is>(params)...); }); }и мы сделали.
index_uptoиindex_overпозволяет работать с пакетами параметров без необходимости генерировать новые внешние перегрузки.конечно, в c++17 вы просто
void delayed_dispatch() { std::apply( func, params ); }теперь, если нам это нравится, в c++14 мы можем напишите:
namespace notstd { template<class T> constexpr auto tuple_size_v = std::tuple_size<T>::value; template<class F, class Tuple> decltype(auto) apply( F&& f, Tuple&& tup ) { auto indexer = index_upto< tuple_size_v<std::remove_reference_t<Tuple>> >(); return indexer( [&](auto...Is)->decltype(auto) { return std::forward<F>(f)( std::get<Is>(std::forward<Tuple>(tup))... ); } ); } }относительно легко и получить пылесос c++17 синтаксис готов к отправке.
void delayed_dispatch() { notstd::apply( func, params ); }просто заменить
notstdСstdкогда ваш компилятор обновляется и Боб ваш дядя.
размышляя о проблеме еще немного, основываясь на данном ответе, я нашел другой способ решения той же проблемы:
template <int N, int M, typename D> struct call_or_recurse; template <typename ...Types> struct dispatcher { template <typename F, typename ...Args> static void impl(F f, const std::tuple<Types...>& params, Args... args) { call_or_recurse<sizeof...(Args), sizeof...(Types), dispatcher<Types...> >::call(f, params, args...); } }; template <int N, int M, typename D> struct call_or_recurse { // recurse again template <typename F, typename T, typename ...Args> static void call(F f, const T& t, Args... args) { D::template impl(f, t, std::get<M-(N+1)>(t), args...); } }; template <int N, typename D> struct call_or_recurse<N,N,D> { // do the call template <typename F, typename T, typename ...Args> static void call(F f, const T&, Args... args) { f(args...); } };что требует изменения реализации
delayed_dispatch()to:void delayed_dispatch() { dispatcher<Args...>::impl(func, params); }это работает путем рекурсивного преобразования
std::tupleв пакет параметров в своем собственном праве.call_or_recurseтребуется в качестве специализации для завершения рекурсии с реальным вызовом, который просто распаковывает завершенный пакет параметров.Я не уверен в этом в любом случае это "лучшее" решение, но это другой способ осмысления и решения.
в качестве другого альтернативного решения вы можете использовать
enable_if, чтобы сформировать что-то, возможно, проще, чем мой предыдущий решение:#include <iostream> #include <functional> #include <tuple> void f(int a, double b, void* c) { std::cout << a << ":" << b << ":" << c << std::endl; } template <typename ...Args> struct save_it_for_later { std::tuple<Args...> params; void (*func)(Args...); template <typename ...Actual> typename std::enable_if<sizeof...(Actual) != sizeof...(Args)>::type delayed_dispatch(Actual&& ...a) { delayed_dispatch(std::forward<Actual>(a)..., std::get<sizeof...(Actual)>(params)); } void delayed_dispatch(Args ...args) { func(args...); } }; int main() { int a=666; double b = -1.234; void *c = NULL; save_it_for_later<int,double,void*> saved = { std::tuple<int,double,void*>(a,b,c), f}; saved.delayed_dispatch(); }первая перегрузка просто берет еще один аргумент из кортежа и помещает его в пакет параметров. Вторая перегрузка принимает соответствующий пакет параметров, а затем делает реальный вызов, причем первая перегрузка отключена в одном и только случай, когда второе было бы жизнеспособным.
мой вариант решения от Johannes с использованием C++14 std:: index_sequence (и тип возврата функции в качестве параметра шаблона RetT):
template <typename RetT, typename ...Args> struct save_it_for_later { RetT (*func)(Args...); std::tuple<Args...> params; save_it_for_later(RetT (*f)(Args...), std::tuple<Args...> par) : func { f }, params { par } {} RetT delayed_dispatch() { return callFunc(std::index_sequence_for<Args...>{}); } template<std::size_t... Is> RetT callFunc(std::index_sequence<Is...>) { return func(std::get<Is>(params) ...); } }; double foo(int x, float y, double z) { return x + y + z; } int testTuple(void) { std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5); save_it_for_later<double, int, float, double> saved (&foo, t); cout << saved.delayed_dispatch() << endl; return 0; }
Comments