Как развернуть кортеж в аргументы функции шаблона переменной?
Рассмотрим случай шаблонной функции с переменными аргументами шаблона:
template<typename Tret, typename... T> Tret func(const T&... t);
Теперь у меня есть кортеж t
ценностей. Как мне позвонить func()
используя значения кортежа в качестве аргументов? Я читал о bind()
функциональный объект, с call()
функция, а также apply()
функционировать в разных устаревших документах. Внедрение GNU GCC 4.4, похоже, имеет call()
функция в bind()
класс, но есть очень мало документации по этому вопросу.
Некоторые люди предлагают рукописные рекурсивные хаки, но истинное значение аргументов вариационных шаблонов - это возможность использовать их в случаях, подобных описанным выше.
У кого-нибудь есть решение или есть подсказка, где почитать об этом?
13 ответов
В C++17 вы можете сделать это:
std::apply(the_function, the_tuple);
Это уже работает в Clang++ 3.9, используя std:: эксперимент::apply.
Отвечая на комментарий о том, что это не будет работать, если the_function
Это временное решение:
#include <tuple>
template <typename T, typename U> void my_func(T &&t, U &&u) {}
int main(int argc, char *argv[argc]) {
std::tuple<int, float> my_tuple;
std::apply([](auto &&... args) { my_func(args...); }, my_tuple);
return 0;
}
Этот обходной путь представляет собой упрощенное решение общей проблемы передачи наборов перегрузок и шаблона функции, где ожидалась бы функция. Общее решение (которое заботится о совершенной пересылке, constexpr-ness и noexcept-ness) представлено здесь: https://blog.tartanllama.xyz/passing-overload-sets/.
Вот мой код, если кому-то интересно
В основном во время компиляции компилятор будет рекурсивно развернуть все аргументы в различных включающих вызовах функций
Предусмотрено 2 версии: одна для функции, вызываемой для объекта, а другая для статической функции.
#include <tr1/tuple>
/**
* Object Function Tuple Argument Unpacking
*
* This recursive template unpacks the tuple parameters into
* variadic template arguments until we reach the count of 0 where the function
* is called with the correct parameters
*
* @tparam N Number of tuple arguments to unroll
*
* @ingroup g_util_tuple
*/
template < uint N >
struct apply_obj_func
{
template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
static void applyTuple( T* pObj,
void (T::*f)( ArgsF... ),
const std::tr1::tuple<ArgsT...>& t,
Args... args )
{
apply_obj_func<N-1>::applyTuple( pObj, f, t, std::tr1::get<N-1>( t ), args... );
}
};
//-----------------------------------------------------------------------------
/**
* Object Function Tuple Argument Unpacking End Point
*
* This recursive template unpacks the tuple parameters into
* variadic template arguments until we reach the count of 0 where the function
* is called with the correct parameters
*
* @ingroup g_util_tuple
*/
template <>
struct apply_obj_func<0>
{
template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
static void applyTuple( T* pObj,
void (T::*f)( ArgsF... ),
const std::tr1::tuple<ArgsT...>& /* t */,
Args... args )
{
(pObj->*f)( args... );
}
};
//-----------------------------------------------------------------------------
/**
* Object Function Call Forwarding Using Tuple Pack Parameters
*/
// Actual apply function
template < typename T, typename... ArgsF, typename... ArgsT >
void applyTuple( T* pObj,
void (T::*f)( ArgsF... ),
std::tr1::tuple<ArgsT...> const& t )
{
apply_obj_func<sizeof...(ArgsT)>::applyTuple( pObj, f, t );
}
//-----------------------------------------------------------------------------
/**
* Static Function Tuple Argument Unpacking
*
* This recursive template unpacks the tuple parameters into
* variadic template arguments until we reach the count of 0 where the function
* is called with the correct parameters
*
* @tparam N Number of tuple arguments to unroll
*
* @ingroup g_util_tuple
*/
template < uint N >
struct apply_func
{
template < typename... ArgsF, typename... ArgsT, typename... Args >
static void applyTuple( void (*f)( ArgsF... ),
const std::tr1::tuple<ArgsT...>& t,
Args... args )
{
apply_func<N-1>::applyTuple( f, t, std::tr1::get<N-1>( t ), args... );
}
};
//-----------------------------------------------------------------------------
/**
* Static Function Tuple Argument Unpacking End Point
*
* This recursive template unpacks the tuple parameters into
* variadic template arguments until we reach the count of 0 where the function
* is called with the correct parameters
*
* @ingroup g_util_tuple
*/
template <>
struct apply_func<0>
{
template < typename... ArgsF, typename... ArgsT, typename... Args >
static void applyTuple( void (*f)( ArgsF... ),
const std::tr1::tuple<ArgsT...>& /* t */,
Args... args )
{
f( args... );
}
};
//-----------------------------------------------------------------------------
/**
* Static Function Call Forwarding Using Tuple Pack Parameters
*/
// Actual apply function
template < typename... ArgsF, typename... ArgsT >
void applyTuple( void (*f)(ArgsF...),
std::tr1::tuple<ArgsT...> const& t )
{
apply_func<sizeof...(ArgsT)>::applyTuple( f, t );
}
// ***************************************
// Usage
// ***************************************
template < typename T, typename... Args >
class Message : public IMessage
{
typedef void (T::*F)( Args... args );
public:
Message( const std::string& name,
T& obj,
F pFunc,
Args... args );
private:
virtual void doDispatch( );
T* pObj_;
F pFunc_;
std::tr1::tuple<Args...> args_;
};
//-----------------------------------------------------------------------------
template < typename T, typename... Args >
Message<T, Args...>::Message( const std::string& name,
T& obj,
F pFunc,
Args... args )
: IMessage( name ),
pObj_( &obj ),
pFunc_( pFunc ),
args_( std::forward<Args>(args)... )
{
}
//-----------------------------------------------------------------------------
template < typename T, typename... Args >
void Message<T, Args...>::doDispatch( )
{
try
{
applyTuple( pObj_, pFunc_, args_ );
}
catch ( std::exception& e )
{
}
}
В C++ есть много способов развернуть / распаковать кортеж и применить эти элементы кортежа к функции шаблона variadic. Вот небольшой вспомогательный класс, который создает индексный массив. Он часто используется в шаблонном метапрограммировании:
// ------------- UTILITY---------------
template<int...> struct index_tuple{};
template<int I, typename IndexTuple, typename... Types>
struct make_indexes_impl;
template<int I, int... Indexes, typename T, typename ... Types>
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...>
{
typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type;
};
template<int I, int... Indexes>
struct make_indexes_impl<I, index_tuple<Indexes...> >
{
typedef index_tuple<Indexes...> type;
};
template<typename ... Types>
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...>
{};
Теперь код, который выполняет эту работу, не такой большой:
// ----------UNPACK TUPLE AND APPLY TO FUNCTION ---------
#include <tuple>
#include <iostream>
using namespace std;
template<class Ret, class... Args, int... Indexes >
Ret apply_helper( Ret (*pf)(Args...), index_tuple< Indexes... >, tuple<Args...>&& tup)
{
return pf( forward<Args>( get<Indexes>(tup))... );
}
template<class Ret, class ... Args>
Ret apply(Ret (*pf)(Args...), const tuple<Args...>& tup)
{
return apply_helper(pf, typename make_indexes<Args...>::type(), tuple<Args...>(tup));
}
template<class Ret, class ... Args>
Ret apply(Ret (*pf)(Args...), tuple<Args...>&& tup)
{
return apply_helper(pf, typename make_indexes<Args...>::type(), forward<tuple<Args...>>(tup));
}
Тест показан ниже:
// --------------------- TEST ------------------
void one(int i, double d)
{
std::cout << "function one(" << i << ", " << d << ");\n";
}
int two(int i)
{
std::cout << "function two(" << i << ");\n";
return i;
}
int main()
{
std::tuple<int, double> tup(23, 4.5);
apply(one, tup);
int d = apply(two, std::make_tuple(2));
return 0;
}
Я не большой специалист по другим языкам, но я предполагаю, что если эти языки не имеют такой функциональности в своем меню, это невозможно сделать. По крайней мере, с C++ вы можете, и я думаю, что это не так сложно...
Я считаю, что это самое элегантное решение (и оно оптимально передается):
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
template<size_t N>
struct Apply {
template<typename F, typename T, typename... A>
static inline auto apply(F && f, T && t, A &&... a)
-> decltype(Apply<N-1>::apply(
::std::forward<F>(f), ::std::forward<T>(t),
::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
))
{
return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
);
}
};
template<>
struct Apply<0> {
template<typename F, typename T, typename... A>
static inline auto apply(F && f, T &&, A &&... a)
-> decltype(::std::forward<F>(f)(::std::forward<A>(a)...))
{
return ::std::forward<F>(f)(::std::forward<A>(a)...);
}
};
template<typename F, typename T>
inline auto apply(F && f, T && t)
-> decltype(Apply< ::std::tuple_size<
typename ::std::decay<T>::type
>::value>::apply(::std::forward<F>(f), ::std::forward<T>(t)))
{
return Apply< ::std::tuple_size<
typename ::std::decay<T>::type
>::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}
Пример использования:
void foo(int i, bool b);
std::tuple<int, bool> t = make_tuple(20, false);
void m()
{
apply(&foo, t);
}
К сожалению, GCC (по крайней мере, 4.6) не удается скомпилировать это с помощью "извините, не реализована: перегрузка искажения" (что просто означает, что компилятор еще не полностью реализует спецификацию C++11), и, поскольку он использует шаблоны с переменным числом аргументов, он не будет работать в MSVC, поэтому он более или менее бесполезен. Однако, как только будет найден компилятор, который поддерживает спецификацию, это будет лучшим подходом IMHO. (Примечание: это не так сложно изменить, чтобы вы могли обойти недостатки в GCC или реализовать его с помощью Boost Preprocessor, но это нарушает элегантность, поэтому эту версию я публикую.)
GCC 4.7 теперь прекрасно поддерживает этот код.
Редактировать: Добавлена пересылка вокруг фактического вызова функции для поддержки справочной формы rvalue * это в случае, если вы используете clang (или если кто-то еще действительно находит способ добавить его).
Редактировать: Добавлено пропущенное вперед по объекту функции в теле функции применения, не являющейся членом. Спасибо pheedbaq за то, что он указал, что его не хватает.
Редактировать: А вот версия C++14, так как она намного лучше (на самом деле еще не компилируется):
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
template<size_t N>
struct Apply {
template<typename F, typename T, typename... A>
static inline auto apply(F && f, T && t, A &&... a) {
return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
);
}
};
template<>
struct Apply<0> {
template<typename F, typename T, typename... A>
static inline auto apply(F && f, T &&, A &&... a) {
return ::std::forward<F>(f)(::std::forward<A>(a)...);
}
};
template<typename F, typename T>
inline auto apply(F && f, T && t) {
return Apply< ::std::tuple_size< ::std::decay_t<T>
>::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}
Вот версия для функций-членов (не очень проверенная!):
using std::forward; // You can change this if you like unreadable code or care hugely about namespace pollution.
template<size_t N>
struct ApplyMember
{
template<typename C, typename F, typename T, typename... A>
static inline auto apply(C&& c, F&& f, T&& t, A&&... a) ->
decltype(ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...))
{
return ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...);
}
};
template<>
struct ApplyMember<0>
{
template<typename C, typename F, typename T, typename... A>
static inline auto apply(C&& c, F&& f, T&&, A&&... a) ->
decltype((forward<C>(c)->*forward<F>(f))(forward<A>(a)...))
{
return (forward<C>(c)->*forward<F>(f))(forward<A>(a)...);
}
};
// C is the class, F is the member function, T is the tuple.
template<typename C, typename F, typename T>
inline auto apply(C&& c, F&& f, T&& t) ->
decltype(ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t)))
{
return ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t));
}
// Example:
class MyClass
{
public:
void foo(int i, bool b);
};
MyClass mc;
std::tuple<int, bool> t = make_tuple(20, false);
void m()
{
apply(&mc, &MyClass::foo, t);
}
template<typename F, typename Tuple, std::size_t ... I>
auto apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename Tuple>
auto apply(F&& f, Tuple&& t) {
using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}
Это адаптировано из проекта C++14 с использованием index_sequence. Я мог бы предложить применить в будущем стандарте (TS).
1) если у вас есть готовая структура parameter_pack в качестве аргумента функции, вы можете просто использовать std::tie следующим образом:
template <class... Args>
void tie_func(std::tuple<Args...> t, Args&... args)
{
std::tie<Args...>(args...) = t;
}
int main()
{
std::tuple<int, double, std::string> t(2, 3.3, "abc");
int i;
double d;
std::string s;
tie_func(t, i, d, s);
std::cout << i << " " << d << " " << s << std::endl;
}
2) если у вас нет готового парампака, вам придется раскрутить кортеж вот так
#include <tuple>
#include <functional>
#include <iostream>
template<int N>
struct apply_wrap {
template<typename R, typename... TupleArgs, typename... UnpackedArgs>
static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>& t, UnpackedArgs... args )
{
return apply_wrap<N-1>::applyTuple( f, t, std::get<N-1>( t ), args... );
}
};
template<>
struct apply_wrap<0>
{
template<typename R, typename... TupleArgs, typename... UnpackedArgs>
static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>&, UnpackedArgs... args )
{
return f( args... );
}
};
template<typename R, typename... TupleArgs>
R applyTuple( std::function<R(TupleArgs...)>& f, std::tuple<TupleArgs...> const& t )
{
return apply_wrap<sizeof...(TupleArgs)>::applyTuple( f, t );
}
int fac(int n)
{
int r=1;
for(int i=2; i<=n; ++i)
r *= i;
return r;
}
int main()
{
auto t = std::make_tuple(5);
auto f = std::function<decltype(fac)>(&fac);
cout << applyTuple(f, t);
}
Все эти реализации хороши. Но из-за использования указателя на компилятор функции-члена часто невозможно встроить вызов целевой функции (по крайней мере, gcc 4.8 не может, несмотря ни на что. Почему gcc не может встроить указатели на функции, которые можно определить?)
Но все изменится, если отправить указатель на функцию-член как аргументы шаблона, а не как параметры функции:
/// from https://stackru.com/a/9288547/1559666
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; };
template<typename TT>
using makeSeq = typename gens< std::tuple_size< typename std::decay<TT>::type >::value >::type;
// deduce function return type
template<class ...Args>
struct fn_type;
template<class ...Args>
struct fn_type< std::tuple<Args...> >{
// will not be called
template<class Self, class Fn>
static auto type_helper(Self &self, Fn f) -> decltype((self.*f)(declval<Args>()...)){
//return (self.*f)(Args()...);
return NULL;
}
};
template<class Self, class ...Args>
struct APPLY_TUPLE{};
template<class Self, class ...Args>
struct APPLY_TUPLE<Self, std::tuple<Args...>>{
Self &self;
APPLY_TUPLE(Self &self): self(self){}
template<class T, T (Self::* f)(Args...), class Tuple>
void delayed_call(Tuple &&list){
caller<T, f, Tuple >(forward<Tuple>(list), makeSeq<Tuple>() );
}
template<class T, T (Self::* f)(Args...), class Tuple, int ...S>
void caller(Tuple &&list, const seq<S...>){
(self.*f)( std::get<S>(forward<Tuple>(list))... );
}
};
#define type_of(val) typename decay<decltype(val)>::type
#define apply_tuple(obj, fname, tuple) \
APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
&decay<decltype(obj)>::type::fname \
> \
(tuple);
И использование:
struct DelayedCall
{
void call_me(int a, int b, int c){
std::cout << a+b+c;
}
void fire(){
tuple<int,int,int> list = make_tuple(1,2,3);
apply_tuple(*this, call_me, list); // even simpler than previous implementations
}
};
Доказательство inlinable http://goo.gl/5UqVnC
С небольшими изменениями мы можем "перегрузить" apply_tuple
:
#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(X,##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define VARARG_IMPL_(base, count, ...) base##count(__VA_ARGS__)
#define VARARG_IMPL(base, count, ...) VARARG_IMPL_(base, count, __VA_ARGS__)
#define VARARG(base, ...) VARARG_IMPL(base, VA_NARGS(__VA_ARGS__), __VA_ARGS__)
#define apply_tuple2(fname, tuple) apply_tuple3(*this, fname, tuple)
#define apply_tuple3(obj, fname, tuple) \
APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
&decay<decltype(obj)>::type::fname \
/* ,decltype(tuple) */> \
(tuple);
#define apply_tuple(...) VARARG(apply_tuple, __VA_ARGS__)
...
apply_tuple(obj, call_me, list);
apply_tuple(call_me, list); // call this->call_me(list....)
Плюс это единственное решение, которое работает с шаблонными функциями.
Новости не выглядят хорошими.
Прочитав только что выпущенный черновой вариант стандарта, я не вижу встроенного решения, которое кажется странным.
Лучшее место, чтобы спросить о таких вещах (если вы еще этого не сделали) - comp.lang.C++. Moderated, потому что некоторые люди регулярно участвуют в составлении стандартного поста.
Если вы ознакомитесь с этой веткой, у кого-то возникнет тот же вопрос (может быть, это вы, в этом случае вы найдете весь этот ответ немного расстраивающим!), И вам будет предложено несколько неприятных реализаций.
Мне просто интересно, будет ли проще заставить функцию принимать tuple
, так как преобразование таким способом проще. Но это подразумевает, что все функции должны принимать кортежи в качестве аргументов для максимальной гибкости, и поэтому это просто демонстрирует странность отсутствия встроенного расширения кортежа в пакет аргументов функции.
Обновление: ссылка выше не работает - попробуйте вставить это:
Расширяя решение @David, вы можете написать рекурсивный шаблон, который
- Не использует (слишком многословно, IMO)
integer_sequence
семантика - Не использует дополнительный временный параметр шаблона
int N
считать рекурсивные итерации - (Необязательно для статических / глобальных функторов) использует функтор в качестве параметра шаблона для оптимизации во время компиляции
Например:
template <class F, F func>
struct static_functor {
template <class... T, class... Args_tmp>
static inline auto apply(const std::tuple<T...>& t, Args_tmp... args)
-> decltype(func(std::declval<T>()...)) {
return static_functor<F,func>::apply(t, args...,
std::get<sizeof...(Args_tmp)>(t));
}
template <class... T>
static inline auto apply(const std::tuple<T...>& t, T... args)
-> decltype(func(args...)) {
return func(args...);
}
};
static_functor<decltype(&myFunc), &myFunc>::apply(my_tuple);
В качестве альтернативы, если ваш функтор не определен во время компиляции (например, неconstexpr
экземпляр functor (или лямбда-выражение), вы можете использовать его как параметр функции вместо параметра шаблона класса и фактически полностью удалить содержащий класс:
template <class F, class... T, class... Args_tmp>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
Args_tmp... args) -> decltype(func(std::declval<T>()...)) {
return apply_functor(func, t, args..., std::get<sizeof...(Args_tmp)>(t));
}
template <class F, class... T>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
T... args) -> decltype(func(args...)) {
return func(args...);
}
apply_functor(&myFunc, my_tuple);
Для вызовов функции указателя на функцию-члена вы можете настроить любой из приведенных выше фрагментов кода так же, как в ответе @David.
объяснение
Что касается второго фрагмента кода, есть две функции шаблона: первая занимает функтор func
кортеж t
с типами T...
и пакет параметров args
типов Args_tmp...
, При вызове он рекурсивно добавляет объекты из t
в пакет параметров по одному, с начала (0
), чтобы завершить, и снова вызывает функцию с новым инкрементным пакетом параметров.
Сигнатура второй функции практически идентична первой, за исключением того, что она использует тип T...
для пакета параметров args
, Таким образом, однажды args
в первой функции полностью заполняются значениями из t
Тип это будет T...
(в псевдо-коде, typeid(T...) == typeid(Args_tmp...)
), и, таким образом, компилятор вместо этого вызовет вторую перегруженную функцию, которая в свою очередь вызывает func(args...)
,
Код в примере статического функтора работает идентично, вместо этого функтор используется в качестве аргумента шаблона класса.
Как насчет этого:
// Warning: NOT tested!
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
using std::declval;
using std::forward;
using std::get;
using std::integral_constant;
using std::size_t;
using std::tuple;
namespace detail
{
template < typename Func, typename ...T, typename ...Args >
auto explode_tuple( integral_constant<size_t, 0u>, tuple<T...> const &t,
Func &&f, Args &&...a )
-> decltype( forward<Func>(f)(declval<T const>()...) )
{ return forward<Func>( f )( forward<Args>(a)... ); }
template < size_t Index, typename Func, typename ...T, typename ...Args >
auto explode_tuple( integral_constant<size_t, Index>, tuple<T...> const&t,
Func &&f, Args &&...a )
-> decltype( forward<Func>(f)(declval<T const>()...) )
{
return explode_tuple( integral_constant<size_t, Index - 1u>{}, t,
forward<Func>(f), get<Index - 1u>(t), forward<Args>(a)... );
}
}
template < typename Func, typename ...T >
auto run_tuple( Func &&f, tuple<T...> const &t )
-> decltype( forward<Func>(f)(declval<T const>()...) )
{
return detail::explode_tuple( integral_constant<size_t, sizeof...(T)>{}, t,
forward<Func>(f) );
}
template < typename Tret, typename ...T >
Tret func_T( tuple<T...> const &t )
{ return run_tuple( &func<Tret, T...>, t ); }
run_tuple
Шаблон функции принимает заданный кортеж и передает его элементы индивидуально данной функции. Он выполняет свою работу путем рекурсивного вызова своих шаблонов вспомогательных функций. explode_tuple
, Важно что run_tuple
передает размер кортежа explode_tuple
; это число действует как счетчик количества элементов для извлечения.
Если кортеж пустой, то run_tuple
называет первую версию explode_tuple
с удаленной функцией в качестве единственного другого аргумента. Удаленная функция вызывается без аргументов, и мы закончили. Если кортеж не пустой, большее число передается второй версии explode_tuple
вместе с дистанционной функцией. Рекурсивный вызов explode_tuple
создается с теми же аргументами, за исключением того, что число счетчиков уменьшается на единицу и (ссылка на) последний элемент кортежа добавляется в качестве аргумента после удаленной функции. В рекурсивном вызове либо счетчик не равен нулю, и выполняется другой вызов с уменьшенным счетчиком, и элемент со следующей ссылкой не вставляется в список аргументов после удаленной функции, но до того, как другие вставленные аргументы или счетчик достигает ноль, и удаленная функция вызывается со всеми аргументами, накопленными после нее.
Я не уверен, что у меня есть синтаксис принудительного использования определенной версии шаблона функции. Я думаю, что вы можете использовать указатель на функцию как функциональный объект; компилятор автоматически исправит это.
Я оцениваю MSVS 2013RC, и в некоторых случаях не удалось собрать некоторые из предыдущих решений, предложенных здесь. Например, MSVS не сможет скомпилировать "автоматические" возвраты, если слишком много параметров функции, из-за ограничения на проникновение в пространство имен (я отправил эту информацию в Microsoft, чтобы исправить ее). В других случаях нам нужен доступ к возврату функции, хотя это также можно сделать с помощью лямды: следующие два примера дают одинаковый результат.
apply_tuple([&ret1](double a){ret1 = cos(a); }, std::make_tuple<double>(.2));
ret2 = apply_tuple((double(*)(double))cos, std::make_tuple<double>(.2));
И еще раз спасибо тем, кто разместил здесь ответы до меня, я бы не добрался до этого без этого... так вот оно:
template<size_t N>
struct apply_impl {
template<typename F, typename T, typename... A>
static inline auto apply_tuple(F&& f, T&& t, A&&... a)
-> decltype(apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
return apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
}
template<typename C, typename F, typename T, typename... A>
static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
-> decltype(apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
return apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
}
};
// This is a work-around for MSVS 2013RC that is required in some cases
#if _MSC_VER <= 1800 /* update this when bug is corrected */
template<>
struct apply_impl<6> {
template<typename F, typename T, typename... A>
static inline auto apply_tuple(F&& f, T&& t, A&&... a)
-> decltype(std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
return std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
}
template<typename C, typename F, typename T, typename... A>
static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
-> decltype((o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
return (o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
}
};
#endif
template<>
struct apply_impl<0> {
template<typename F, typename T, typename... A>
static inline auto apply_tuple(F&& f, T&&, A&&... a)
-> decltype(std::forward<F>(f)(std::forward<A>(a)...)) {
return std::forward<F>(f)(std::forward<A>(a)...);
}
template<typename C, typename F, typename T, typename... A>
static inline auto apply_tuple(C*const o, F&& f, T&&, A&&... a)
-> decltype((o->*std::forward<F>(f))(std::forward<A>(a)...)) {
return (o->*std::forward<F>(f))(std::forward<A>(a)...);
}
};
// Apply tuple parameters on a non-member or static-member function by perfect forwarding
template<typename F, typename T>
inline auto apply_tuple(F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t))) {
return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t));
}
// Apply tuple parameters on a member function
template<typename C, typename F, typename T>
inline auto apply_tuple(C*const o, F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t))) {
return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t));
}
Почему бы просто не обернуть ваши переменные аргументы в класс кортежей, а затем использовать рекурсию времени компиляции (см. Ссылку), чтобы получить интересующий вас индекс. Я считаю, что распаковка шаблонов переменных в контейнер или коллекцию может быть небезопасной по сравнению с гетерогенными типами
template<typename... Args>
auto get_args_as_tuple(Args... args) -> std::tuple<Args...>
{
return std::make_tuple(args);
}
Это простое решение работает для меня:
template<typename... T>
void unwrap_tuple(std::tuple<T...>* tp)
{
std::cout << "And here I have the tuple types, all " << sizeof...(T) << " of them" << std::endl;
}
int main()
{
using TupleType = std::tuple<int, float, std::string, void*>;
unwrap_tuple((TupleType*)nullptr); // trick compiler into using template param deduction
}