Подтвердить что ты не робот

Вызов функции для каждого вариационного аргумента шаблона и массива

Итак, у меня есть тип X:

typedef ... X;

и функции шаблона f:

class <typename T>
void f(X& x_out, const T& arg_in);

а затем функцию g:

void g(const X* x_array, size_t x_array_size);

Мне нужно написать переменную функцию шаблона h, которая делает это:

template<typename... Args>
void h(Args... args)
{
    constexpr size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    for (int i = 0; i < nargs; i++) // foreach arg
        f(x_array[i], args[i]); // call f (doesn't work)

    g(x_array, nargs); // call g with x_array
}

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

Каков наилучший метод для замены средней части h?

И победитель Xeo:

template<class T> X fv(const T& t) { X x; f(x,t); return x; }

template<class... Args>
void h(Args... args)
{
  X x_array[] = { fv(args)... };

  g(x_array, sizeof...(Args));
}

(На самом деле в моем конкретном случае я могу переписать f, чтобы возвращать x по значению, а не как параметр out, поэтому мне даже не нужно fv выше)

4b9b3361

Ответ 1

Вы можете реорганизовать или обернуть f, чтобы вернуть новый X вместо того, чтобы его передать, так как это приведет к расширению пакета в руку и сделает функцию очень кратким:

template<class T>
X fw(T const& t){ X x; f(x, t); return x; }

template<class... Args>
void h(Args... args){
  X xs[] = { fw(args)... };
  g(xs, sizeof...(Args));
}

Пример в реальном времени.

И если бы вы могли изменить g, чтобы просто принять std::initializer_list, это будет еще более кратким:

template<class... Args>
void h(Args... args){
  g({f(args)...});
}

Пример в реальном времени. Или (возможно, лучше), вы также можете предоставить только оболочку g, которая перейдет в реальный g:

void g(X const*, unsigned){}

void g(std::initializer_list<X> const& xs){ g(xs.begin(), xs.size()); }

template<class... Args>
void h(Args... args){
  g({f(args)...});
}

Пример в реальном времени.
Изменить: Другая опция использует временный массив:

template<class T>
using Alias = T;

template<class T>
T& as_lvalue(T&& v){ return v; }

template<class... Args>
void h(Args... args){
  g(as_lvalue(Alias<X[]>{f(args)...}), sizeof...(Args));
}

Пример в реальном времени. Обратите внимание, что функция as_lvalue опасна, массив по-прежнему живет только до конца полного выражения (в данном случае g), поэтому будьте осторожны при его использовании. Alias необходимо, так как только X[]{ ... } не допускается из-за языковой грамматики.

Если все это невозможно, вам понадобится рекурсия для доступа ко всем элементам пакета args.

#include <tuple>

template<unsigned> struct uint_{}; // compile-time integer for "iteration"

template<unsigned N, class Tuple>
void h_helper(X (&)[N], Tuple const&, uint_<N>){}

template<unsigned N, class Tuple, unsigned I = 0>
void h_helper(X (&xs)[N], Tuple const& args, uint_<I> = {}){
  f(xs[I], std::get<I>(args));
  h_helper(xs, args, uint_<I+1>());
}

template<typename... Args>
void h(Args... args)
{
    static constexpr unsigned nargs = sizeof...(Args);
    X xs[nargs];

    h_helper(xs, std::tie(args...));

    g(xs, nargs);
}

Пример в реальном времени.

Изменить: Вдохновленный комментарием ecatmur, я использовал трюк индексов , чтобы он работал с просто расширением пакета и с f и g as-is, не изменяя их.

template<unsigned... Indices>
struct indices{
  using next = indices<Indices..., sizeof...(Indices)>;
};
template<unsigned N>
struct build_indices{
  using type = typename build_indices<N-1>::type::next;
};
template <>
struct build_indices<0>{
  using type = indices<>;
};
template<unsigned N>
using IndicesFor = typename build_indices<N>::type;

template<unsigned N, unsigned... Is, class... Args>
void f_them_all(X (&xs)[N], indices<Is...>, Args... args){
  int unused[] = {(f(xs[Is], args), 1)...};
  (void)unused;
}

template<class... Args>
void h(Args... args){
  static constexpr unsigned nargs = sizeof...(Args);
  X xs[nargs];
  f_them_all(xs, IndicesFor<nargs>(), args...);
  g(xs, nargs);
}

Пример в реальном времени.

Ответ 2

Это очевидно: вы не используете итерацию, а рекурсию. Когда речь идет о вариативных шаблонах, всегда приходит что-то рекурсивное. Даже при привязке элементов к std::tuple<...> с помощью tie() оно рекурсивно: просто случается, что рекурсивный бизнес выполняется кортежем. В вашем случае кажется, что вам нужно что-то вроде этого (возможно, существует несколько опечаток, но в целом это должно работать):

template <int Index, int Size>
void h_aux(X (&)[Size]) {
}

template <int Index, int Size, typename Arg, typename... Args>
void h_aux(X (&xs)[Size], Arg arg, Args... args) {
    f(xs[Index], arg);
    h_aux<Index + 1, Size>(xs, args...);
}

template <typename... Args>
void h(Args... args)
{
    X xs[sizeof...(args)];
    h_aux<0, sizeof...(args)>(xs, args...);
    g(xs, sizeof...(args));
}

Я думаю, вы не сможете использовать nargs для определения размера массива: Nothing указывает компилятору, что оно должно быть постоянным выражением.

Ответ 3

Это довольно просто сделать с расширением пакета параметров, даже если вы не можете переписать f, чтобы вернуть выходной параметр по значению:

struct pass { template<typename ...T> pass(T...) {} };

template<typename... Args>
void h(Args... args)
{
    const size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    X *x = x_array;
    int unused[]{(f(*x++, args), 1)...}; // call f
    pass{unused};

    g(x_array, nargs); // call g with x_array
}

Должно быть возможно просто написать

    pass{(f(*x++, args), 1)...}; // call f

но похоже, что g++ (по крайней мере, 4.7.1) имеет ошибку, в которой ему не удается упорядочить оценку параметров списка-инициализатора в качестве инициализаторов классов. Инициализаторы массива в порядке, хотя; см. Последовательность между вариационным расширением для получения дополнительной информации и примеров.

Живой пример.


В качестве альтернативы, здесь метод, упомянутый Xeo с использованием сгенерированного пакета индексов; к сожалению, для этого требуется дополнительный вызов функции и параметр, но он достаточно элегантен (особенно если у вас есть генератор индексного пакета):

template<int... I> struct index {
    template<int n> using append = index<I..., n>; };
template<int N> struct make_index { typedef typename
    make_index<N - 1>::type::template append<N - 1> type; };
template<> struct make_index<0> { typedef index<> type; };
template<int N> using indexer = typename make_index<N>::type;

template<typename... Args, int... i>
void h2(index<i...>, Args... args)
{
    const size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    pass{(f(x_array[i], args), 1)...}; // call f

    g(x_array, nargs); // call g with x_array
}

template<typename... Args>
void h(Args... args)
{
  h2(indexer<sizeof...(args)>(), std::forward<Args>(args)...);
}

См. С++ 11: я могу перейти от нескольких аргументов к кортежу, но могу ли я перейти от кортежа к нескольким аргументам? для получения дополнительной информации. Живой пример.

Ответ 4

Хороший шаблон как ответ для первой части вопроса:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) {
    [](...){}((f(std::forward<Args>(args)), 0)...);
}

Ответ 5

Xeo находится на правильной идее - вы хотите создать какой-то "вариационный итератор", который скрывает много этой гадости от остальной части кода.

Я бы взял файл индекса и спрятал его за интерфейсом итератора, смоделированным после std::vector, так как std:: tuple также является линейным контейнером для данных. Затем вы можете просто повторно использовать все свои вариационные функции и классы, не имея явно рекурсивного кода где-нибудь еще.