Как развернуть кортеж в аргументы функции шаблона переменной?

136

Рассмотрим случай шаблонной функции с переменными аргументами шаблона:

template<typename Tret, typename... T> Tret func(const T&... t);

Теперь у меня есть кортеж tзначений. Как мне вызвать, func()используя значения кортежа в качестве аргументов? Я читал об bind()объекте функции с call()функцией, а также о apply()функции в различных устаревших документах. Реализация GNU GCC 4.4, кажется, имеет call()функцию в bind()классе, но документации по этому вопросу очень мало.

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

У кого-нибудь есть решение или есть подсказка, где почитать об этом?

Xeo
источник
5
Стандарт C ++ 14 имеет решение см. open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
Skeen
1
Идея заключается в том , чтобы распаковать кортеж в одном VARIADIC взрыва, используя integer_sequenceсм en.cppreference.com/w/cpp/utility/integer_sequence
Skeen
6
Имея an integer_sequence S, вы просто вызываете свою функцию как func(std::get<S>(tuple)...)и позволяете компилятору обрабатывать все остальное.
Скин
1
Если используется C ++ 17 или более поздняя
lewis

Ответы:

46

Вот мой код, если кому-то интересно

В основном во время компиляции компилятор будет рекурсивно развернуть все аргументы в различных включающих вызовах функций <N> -> вызовов <N-1> -> вызовов ... -> вызовов <0>, который является последним, и компилятор будет оптимизировать различные промежуточные вызовы функций, чтобы сохранить только последний, который является эквивалентом func (arg1, arg2, arg3, ...)

Предусмотрено 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 )
  {

  }
}
Дэвид
источник
2
Можно ли адаптировать это для работы в случае, когда рассматриваемая «функция» на самом деле является конструктором?
HighCommander4
Не могли бы вы привести пример того, что вы хотите сделать, и мы можем пойти оттуда.
Дэвид
Это решение обеспечивает только накладные расходы времени компиляции, и в конце оно будет упрощено до (pObj -> * f) (arg0, arg, 1, ... argN); право?
Тупой
да, компилятор сжимает несколько вызовов функций в последний, как если бы вы написали его самостоятельно, что является прелестью всего этого мета-программирования.
Дэвид
все tr1вещи теперь можно удалить с помощью c ++ 11
Райан Хейнинг,
37

В 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/ .

Мохаммад Алагган
источник
В соответствии с примером кода в std :: apply он, кажется, не работает, если the_functionшаблонизирован.
Цитракс
1
@Zitrax Вы можете указать аргументы шаблона функции:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
Эрбурет говорит восстановить Монику
Это самое простое, самое элегантное решение. И это творит чудеса. Большое спасибо, М. Алагган !!!!!! +100 голосов
Эллиотт
36

В 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 ++ вы можете, и я думаю, что это не так сложно ...

sigidagi
источник
msgstr "и применить эти элементы кортежа к функции шаблона переменной" . Тем не менее, тестовый раздел содержит только не шаблонные функции. Если я добавлю один лайк template<class ... T> void three(T...) {}и попробую использовать apply, он не скомпилируется.
Цитракс
32

Я считаю, что это самое элегантное решение (и оно оптимально передается):

#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);
}
DRayX
источник
1
+1 из перечисленных ответов, ваш был ближе всего к работе с аргументами, аргументы которых являются векторами ... ... но я все еще получаю ошибки компиляции. ideone.com/xH5kBH Если вы скомпилируете это с -DDIRECT_CALL и запустите его, вы увидите, каким должен быть вывод. В противном случае я получаю ошибку компиляции (я думаю, что decltype недостаточно умен, чтобы выяснить мой особый случай), с gcc 4.7.2.
kfmfe04 13.12.12
3
Версия gcc для ideaone является старой для этого, она не поддерживает искаженную перегрузку возвращаемого типа decltype. Я довольно тщательно протестировал этот код в gcc 4.7.2, и у меня не возникло никаких проблем. В gcc 4.8 вы можете использовать новую функцию автоматического возврата значений в C ++ 17, чтобы избежать всех неприятных конечных типов возврата decltype.
DRayX 18.12.12
1
Из любопытства, в applyфункции, fне являющейся членом , почему не заключено в std::forwardвызов, как в возвращаемом типе? Разве это не нужно?
Бретт Россиер
3
Из любопытства я попытался скомпилировать это в GCC 4.8 и foo('x', true)скомпилировать с точно таким же кодом сборки, что и apply(foo, ::std::make_tuple('x', true))с любым уровнем оптимизации, кроме -O0.
DRayX
2
С C ++ 14 integer_sequenceвы даже получаете почти правильную реализацию apply()в своем примере. см. мой ответ ниже.
PeterSom
28
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).

PeterSom
источник
1

Новости не выглядят хорошими.

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

Лучшее место, чтобы спросить о таких вещах (если вы еще этого не сделали) - comp.lang.c ++. Moderated, потому что некоторые люди регулярно участвуют в составлении стандартного поста.

Если вы проверите эту тему , у кого-то возникнет тот же вопрос (может, это вы, и в этом случае вы найдете весь этот ответ немного расстраивающим!), И вам предложат несколько неприятных реализаций.

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

Обновление: ссылка выше не работает - попробуйте вставить это:

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/750fa3815cdaac45/d8dc09e34bbb9661?lnk=gst&q=tuple+variadic#d8dc09e34bbb9661

Дэниел Уорвикер
источник
Я удивляюсь, почему они вообще не имеют единого представления о наборе аргументов для кортежей и функций. Возможно, в соответствующем компиляторе они взаимозаменяемы, но я нигде не обнаружил указаний на то, что где-то читал о них.
Даниэль Уорвикер
2
Поскольку tuple <int, char, string> необходим как отдельный тип; как и возможность создавать функцию, которая не требует make_type в середине каждого вызова.
Коппро
1
Кроме того, лучшее место не comp.lang.c ++. Модерируется. Вопросы о C ++ 1x почти всегда лучше адресованы comp.std.c ++.
Коппро
1

Все эти реализации хороши. Но из-за использования указателя на компилятор функции-члена часто невозможно встроить вызов целевой функции (по крайней мере, gcc 4.8 не может, несмотря ни на что. Почему gcc не может встроить указатели на функции, которые можно определить? )

Но все изменится, если отправить указатель на функцию-член как аргументы шаблона, а не как параметры функции:

/// from https://stackoverflow.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....)

Плюс это единственное решение, которое работает с шаблонными функциями.

tower120
источник
1

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);
}
SDD
источник
0

Как насчет этого:

// 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создается с теми же аргументами, за исключением того, что число счетчиков уменьшается на единицу и (ссылка на) последний элемент кортежа добавляется в качестве аргумента после удаленной функции. В рекурсивном вызове либо счетчик не равен нулю, и выполняется другой вызов с уменьшенным счетчиком, и элемент со следующей ссылкой не вставляется в список аргументов после удаленной функции, но до того, как другие вставленные аргументы или счетчик достигает ноль и удаленная функция вызывается со всеми аргументами, накопленными после нее.

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

CTMacUser
источник
0

Я оцениваю 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));
}
lap777
источник
Почему вы делаете объектный аргумент константным указателем? Не ссылка, не ссылка const, не просто указатель? Что если вызываемой функции не будет const?
tower120
0

Расширяя решение @ David, вы можете написать рекурсивный шаблон, который

  1. Не использует (слишком многословную, imo) integer_sequenceсемантику
  2. Не использует дополнительный временный параметр шаблона int Nдля подсчета рекурсивных итераций
  3. (Необязательно для статических / глобальных функторов) использует функтор в качестве параметра шаблона для оптимизации во время компиляции

Например:

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экземпляр без функтора или лямбда-выражение), вы можете использовать его как параметр функции вместо параметра шаблона класса и фактически полностью удалить содержащий класс:

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...).

Код в примере статического функтора работает идентично, вместо этого функтор используется в качестве аргумента шаблона класса.

CrepeGoat
источник
Будем благодарны за любые комментарии по оптимизации во время компиляции первого варианта, поэтому я могу сделать свой ответ более полным (и, возможно, узнать что-то новое).
CrepeGoat
-3

Почему бы просто не обернуть свои переменные аргументы в класс кортежей, а затем использовать рекурсию времени компиляции (см. Ссылку ), чтобы получить интересующий вас индекс. Я считаю, что распаковка шаблонов переменных в контейнер или коллекцию может быть небезопасной по сравнению с гетерогенными типами

template<typename... Args>
auto get_args_as_tuple(Args... args) -> std::tuple<Args...> 
{
    return std::make_tuple(args);
}
outro56
источник
6
Вопрос был наоборот. Не Args...-> tuple, но tuple-> Args....
Xeo
-4

Это простое решение работает для меня:

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
}
Maciek Gajewski
источник