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

ПОМЕЩЕНИЕ:

Немного поигравшись с вариативными шаблонами, я понял, что достижение чего-либо, что выходит за рамки тривиальных задач метапрограммирования, вскоре становится довольно громоздким. В частности, я обнаружил, что мне нужен способ выполнения общих операций над пакетом аргументов, таких как iterate, split, loop < / em> в стиле std::for_each и так далее.

Посмотрев эту лекцию, Андрей Александреску из C ++ и после 2012 г. о желательности static if в C ++ (конструкция, заимствованная из языка программирования D) У меня было ощущение, что мне пригодятся какие-нибудь static for - и я чувствую, что больше этих static конструкций может принести пользу.

Поэтому я начал задаваться вопросом, есть ли способ добиться чего-то подобного для пакетов аргументов функции вариативного шаблона (псевдокод):

template<typename... Ts>
void my_function(Ts&&... args)
{
    static for (int i = 0; i < sizeof...(args); i++) // PSEUDO-CODE!
    {
        foo(nth_value_of<i>(args));
    }
}

Что будет преобразовано во время компиляции во что-то вроде этого:

template<typename... Ts>
void my_function(Ts&&... args)
{
    foo(nth_value_of<0>(args));
    foo(nth_value_of<1>(args));
    // ...
    foo(nth_value_of<sizeof...(args) - 1>(args));
}

В принципе, static_for допускает еще более сложную обработку:

template<typename... Ts>
void foo(Ts&&... args)
{
    constexpr s = sizeof...(args);

    static for (int i = 0; i < s / 2; i++)
    {
        // Do something
        foo(nth_value_of<i>(args));
    }

    static for (int i = s / 2; i < s; i++)
    {
        // Do something different
        bar(nth_value_of<i>(args));
    }
}

Или для более выразительной идиомы вроде этой:

template<typename... Ts>
void foo(Ts&&... args)
{
    static for_each (auto&& x : args)
    {
        foo(x);
    }
}

СМЕЖНЫЕ РАБОТЫ:

Я поискал в Интернете и обнаружил, что что-то действительно существует:

  • Эта ссылка описывает, как преобразовать пакет параметров в вектор Boost.MPL, но это идет только наполовину (если не меньше) к цели;
  • этот вопрос о SO, похоже, требует аналогичной и немного связанной функции метапрограммирования (разделение пакет аргументов на две половины) - на самом деле, есть несколько вопросов по SO, которые, похоже, связаны с этой проблемой, но ни один из ответов, которые я прочитал, не решает ее удовлетворительно ИМХО;
  • Boost.Fusion defines algorithms for converting an argument pack into a tuple, but I would prefer:
    1. not to create unnecessary temporaries to hold arguments that can (and should be) perfectly forwarded to some generic algorithms;
    2. иметь для этого небольшую автономную библиотеку, в то время как Boost.Fusion, вероятно, будет включать в себя гораздо больше вещей, чем необходимо для решения этой проблемы.

ВОПРОС:

Есть ли относительно простой способ, возможно, с помощью некоторого метапрограммирования шаблонов, достичь того, что я ищу, без ограничений существующих подходов?


person Andy Prowl    schedule 10.01.2013    source источник
comment
Если foo что-то возвращает, вы можете просто написать eat (foo (args) ...), где eat - это функция, которая ничего не делает со своими аргументами. Требуется некоторая настройка для функций, которые возвращают void, или если вы хотите указать порядок выполнения (это обсуждалось на usenet, вероятно, comp.lang.c ++. Moderated, хотя я не могу его найти в данный момент). Обсуждалось разрешить foo(args);...   -  person Marc Glisse    schedule 10.01.2013
comment
@MarcGlisse: Вы правы, и я действительно пробовал это сделать. Проблема с этим решением заключается в том, что C ++ не гарантирует какой-либо порядок для оценки аргументов функции, что часто требуется при итерации (помимо того факта, что функции должны возвращать значение, даже если они не требуются, но это мелочь).   -  person Andy Prowl    schedule 10.01.2013
comment
Думаю, есть вариант, где указан порядок оценки аргументов, может внутри {} (список инициализаторов) они есть. Что касается возвращаемого типа, вы, вероятно, можете сделать (foo (args), 0) ... или какой-нибудь другой трюк.   -  person Marc Glisse    schedule 10.01.2013
comment
@MarcGlisse: это может быть так, пожалуйста, попробуйте и улучшите мою библиотеку, если хотите. Я был бы рад, если бы его можно было улучшить. честно говоря, я думаю, что мое решение не накладывает никаких накладных расходов и не накладывает никаких ограничений на клиентскую сторону, что хорошо для моих целей. но это не значит, что он идеален, конечно   -  person Andy Prowl    schedule 10.01.2013
comment
@MarcGlisse: Чтобы выполнить вызов функции по порядку, можно использовать это: auto eat[] = {foo(args)...};. тогда порядок гарантированно будет слева направо.   -  person Nawaz    schedule 10.01.2013
comment
@Nawaz: разве это не выделит массив, содержащий результаты каждого foo(arg)? кроме того, что, если типы возврата foo() различаются для разных типов arg?   -  person Andy Prowl    schedule 10.01.2013
comment
@Nawaz: спасибо за подтверждение. AndyProwl: библиотечное решение - это нормально, я просто рассматривал случаи, когда можно обойтись без, но в большом проекте я бы обязательно использовал библиотеку.   -  person Marc Glisse    schedule 10.01.2013
comment
@AndyProwl: Если тип возвращаемого значения различается для каждой перегрузки foo, тогда все должно быть в порядке: std::tuple<Args...> eat {foo(args)...};.   -  person Nawaz    schedule 10.01.2013
comment
@Nawaz: Понятно, но таким образом я бы создал временный кортеж, которого я хотел избежать, поскольку он генерирует ненужные копии / перемещает   -  person Andy Prowl    schedule 10.01.2013
comment
@AndyProwl: я не знаю, но компиляторы могут оптимизировать этот шаг (с точки зрения памяти), если не сейчас, то в будущих версиях, потому что я подозреваю, что такие шаблоны будут часто появляться.   -  person Nawaz    schedule 10.01.2013
comment
@Nawaz: Понятно. ну, если бы это было так и преобразование из пакета в кортеж было бы абсолютно недорогим с точки зрения памяти и вычислений, тогда может иметь смысл переработать алгоритмы моей библиотеки для работы с кортежами, а не с пакетами аргументов, потому что концепция расширяется в кортежи довольно просто. но опять же Boost.Fusion, вероятно, работает лучше, чем я когда-либо, когда дело доходит до кортежей   -  person Andy Prowl    schedule 10.01.2013
comment
FWIW, в прошлом я уже проверял, что и GCC, и VC ++ полностью оптимизируют кортежи ссылок в сборках релизов - идентичный кодогенератор, чтобы не использовать кортежи (тестирование с Boost.Fusion).   -  person ildjarn    schedule 16.01.2013
comment
@ildjarn: это полезная информация. это делает мою работу менее полезной, но все же полезной информацией. Спасибо   -  person Andy Prowl    schedule 16.01.2013
comment
Примечание: в python есть функция, которая выполняет итерацию по итератору и возвращает оба элемента с его индексом, она называется enumerate. Я бы подумал об изменении имени с for_each, которое (в стандартной библиотеке) не содержит индексов, на что-то другое ... например, enumerate, и пусть функтор принимает как элемент, так и индекс (и, возможно, общий размер как хорошо ?) :)   -  person Matthieu M.    schedule 13.02.2013
comment
Я снова читал ваш вопрос (и ваш ответ), и я заметил, что ваши требования могут быть выполнены с помощью моего времени компиляции вычислительная библиотека во время компиляции. Это не совсем то же самое, но это та же концепция :)   -  person Manu343726    schedule 29.09.2013
comment
Это проблема, для решения которой была разработана Hana: Boost.Hana - это библиотека комбинаторов, адаптированная для манипулирование разнородными коллекциями (подумайте, std :: tuple). Он обеспечивает операции высокого уровня для управления этими коллекциями эффективными способами во время компиляции и с большим уровнем выразительности. Одна из его основных целей - объединить программирование на уровне типов (подумайте о Boost.MPL) и разнородное программирование на уровне значений (подумайте о Boost.Fusion) в едином согласованном интерфейсе, позволяя миру времени компиляции и среды выполнения взаимодействовать в новых, полезных способами.   -  person void-pointer    schedule 29.11.2014


Ответы (5)


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

  • Позволяет перебирать все или некоторые элементы пакета аргументов, возможно, заданных путем вычисления их индексов в пакете;
  • Позволяет пересылать вычисленные части пакета аргументов вариативным функторам;
  • Требуется только включение одного относительно короткого файла заголовка;
  • Широко использует идеальную пересылку, чтобы обеспечить интенсивное встраивание и избежать ненужных копий / перемещений, чтобы минимизировать потерю производительности;
  • Внутренняя реализация итерационных алгоритмов полагается на оптимизацию пустого базового класса для минимизации потребления памяти;
  • Его легко (относительно, учитывая метапрограммирование шаблонов) расширять и адаптировать.

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

ИСПОЛЬЗОВАНИЕ

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

// Simple functor with a generic call operator that prints its input. This is used by the
// following functors and by some demonstrative test cases in the main() routine.
struct print
{
    template<typename T>
    void operator () (T&& t)
    {
        cout << t << endl;
    }
};

// This shows how a for_each_*** helper can be used inside a variadic template function
template<typename... Ts>
void print_all(Ts&&... args)
{
    for_each_in_arg_pack(print(), forward<Ts>(args)...);
}

Вышеупомянутый функтор print также можно использовать в более сложных вычислениях. В частности, вот как можно перебирать подмножество (в данном случае поддиапазон) аргументов в пакете:

// Shows how to select portions of an argument pack and 
// invoke a functor for each of the selected elements
template<typename... Ts>
void split_and_print(Ts&&... args)
{
    constexpr size_t packSize = sizeof...(args);
    constexpr size_t halfSize = packSize / 2;

    cout << "Printing first half:" << endl;
    for_each_in_arg_pack_subset(
        print(), // The functor to invoke for each element
        index_range<0, halfSize>(), // The indices to select
        forward<Ts>(args)... // The argument pack
        );

    cout << "Printing second half:" << endl;
    for_each_in_arg_pack_subset(
        print(), // The functor to invoke for each element
        index_range<halfSize, packSize>(), // The indices to select
        forward<Ts>(args)... // The argument pack
        );
}

Иногда может потребоваться просто перенаправить часть пакета аргументов в какой-либо другой вариадический функтор вместо того, чтобы перебирать его элементы и передавать каждый из них индивидуально в невариадический функтор. Вот что позволяет делать алгоритм forward_subpack():

// Functor with variadic call operator that shows the usage of for_each_*** 
// to print all the arguments of a heterogeneous pack
struct my_func
{
    template<typename... Ts>
    void operator ()(Ts&&... args)
    {
        print_all(forward<Ts>(args)...);
    }
};

// Shows how to forward only a portion of an argument pack 
// to another variadic functor
template<typename... Ts>
void split_and_print(Ts&&... args)
{
    constexpr size_t packSize = sizeof...(args);
    constexpr size_t halfSize = packSize / 2;

    cout << "Printing first half:" << endl;
    forward_subpack(my_func(), index_range<0, halfSize>(), forward<Ts>(args)...);

    cout << "Printing second half:" << endl;
    forward_subpack(my_func(), index_range<halfSize, packSize>(), forward<Ts>(args)...);
}

Для более конкретных задач, конечно, можно получить определенные аргументы в пакете, проиндексировав их. Вот что позволяет делать функция nth_value_of() вместе со своими помощниками first_value_of() и last_value_of():

// Shows that arguments in a pack can be indexed
template<unsigned I, typename... Ts>
void print_first_last_and_indexed(Ts&&... args)
{
    cout << "First argument: " << first_value_of(forward<Ts>(args)...) << endl;
    cout << "Last argument: " << last_value_of(forward<Ts>(args)...) << endl;
    cout << "Argument #" << I << ": " << nth_value_of<I>(forward<Ts>(args)...) << endl;
}

С другой стороны, если пакет аргументов однороден (т. Е. Все аргументы имеют один и тот же тип), предпочтительнее может быть формулировка, подобная приведенной ниже. Мета-функция is_homogeneous_pack<> позволяет определить, все ли типы в пакете параметров однородны, и в основном предназначена для использования в операторах static_assert():

// Shows the use of range-based for loops to iterate over a
// homogeneous argument pack
template<typename... Ts>
void print_all(Ts&&... args)
{
    static_assert(
        is_homogeneous_pack<Ts...>::value, 
        "Template parameter pack not homogeneous!"
        );

    for (auto&& x : { args... })
    {
        // Do something with x...
    }

    cout << endl;
}

Наконец, поскольку лямбда-выражения - это всего лишь синтаксический сахар для функторов, их также можно использовать в сочетании с описанными выше алгоритмами; однако до тех пор, пока в C ++ не будут поддерживаться общие лямбда-выражения, это возможно только для однородных пакетов аргументов. В следующем примере также показано использование мета-функции homogeneous-type<>, которая возвращает тип всех аргументов в однородном пакете:

 // ...
 static_assert(
     is_homogeneous_pack<Ts...>::value, 
     "Template parameter pack not homogeneous!"
     );
 using type = homogeneous_type<Ts...>::type;
 for_each_in_arg_pack([] (type const& x) { cout << x << endl; }, forward<Ts>(args)...);

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

РЕАЛИЗАЦИЯ

Теперь идет реализация, которая сама по себе немного сложна, поэтому я буду полагаться на комментарии, чтобы объяснить код и не делать этот пост слишком длинным (возможно, это уже так):

#include <type_traits>
#include <utility>

//===============================================================================
// META-FUNCTIONS FOR EXTRACTING THE n-th TYPE OF A PARAMETER PACK

// Declare primary template
template<int I, typename... Ts>
struct nth_type_of
{
};

// Base step
template<typename T, typename... Ts>
struct nth_type_of<0, T, Ts...>
{
    using type = T;
};

// Induction step
template<int I, typename T, typename... Ts>
struct nth_type_of<I, T, Ts...>
{
    using type = typename nth_type_of<I - 1, Ts...>::type;
};

// Helper meta-function for retrieving the first type in a parameter pack
template<typename... Ts>
struct first_type_of
{
    using type = typename nth_type_of<0, Ts...>::type;
};

// Helper meta-function for retrieving the last type in a parameter pack
template<typename... Ts>
struct last_type_of
{
    using type = typename nth_type_of<sizeof...(Ts) - 1, Ts...>::type;
};

//===============================================================================
// FUNCTIONS FOR EXTRACTING THE n-th VALUE OF AN ARGUMENT PACK

// Base step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
    typename std::enable_if<(I == 0), decltype(std::forward<T>(t))>::type
{
    return std::forward<T>(t);
}

// Induction step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
    typename std::enable_if<(I > 0), decltype(
        std::forward<typename nth_type_of<I, T, Ts...>::type>(
            std::declval<typename nth_type_of<I, T, Ts...>::type>()
            )
        )>::type
{
    using return_type = typename nth_type_of<I, T, Ts...>::type;
    return std::forward<return_type>(nth_value_of<I - 1>((std::forward<Ts>(args))...));
}

// Helper function for retrieving the first value of an argument pack
template<typename... Ts>
auto first_value_of(Ts&&... args) ->
    decltype(
        std::forward<typename first_type_of<Ts...>::type>(
            std::declval<typename first_type_of<Ts...>::type>()
            )
        )
{
    using return_type = typename first_type_of<Ts...>::type;
    return std::forward<return_type>(nth_value_of<0>((std::forward<Ts>(args))...));
}

// Helper function for retrieving the last value of an argument pack
template<typename... Ts>
auto last_value_of(Ts&&... args) ->
    decltype(
        std::forward<typename last_type_of<Ts...>::type>(
            std::declval<typename last_type_of<Ts...>::type>()
            )
        )
{
    using return_type = typename last_type_of<Ts...>::type;
    return std::forward<return_type>(nth_value_of<sizeof...(Ts) - 1>((std::forward<Ts>(args))...));
}

//===============================================================================
// METAFUNCTION FOR COMPUTING THE UNDERLYING TYPE OF HOMOGENEOUS PARAMETER PACKS

// Used as the underlying type of non-homogeneous parameter packs
struct null_type
{
};

// Declare primary template
template<typename... Ts>
struct homogeneous_type;

// Base step
template<typename T>
struct homogeneous_type<T>
{
    using type = T;
    static const bool isHomogeneous = true;
};

// Induction step
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
    // The underlying type of the tail of the parameter pack
    using type_of_remaining_parameters = typename homogeneous_type<Ts...>::type;

    // True if each parameter in the pack has the same type
    static const bool isHomogeneous = std::is_same<T, type_of_remaining_parameters>::value;

    // If isHomogeneous is "false", the underlying type is the fictitious null_type
    using type = typename std::conditional<isHomogeneous, T, null_type>::type;
};

// Meta-function to determine if a parameter pack is homogeneous
template<typename... Ts>
struct is_homogeneous_pack
{
    static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};

//===============================================================================
// META-FUNCTIONS FOR CREATING INDEX LISTS

// The structure that encapsulates index lists
template <unsigned... Is>
struct index_list
{
};

// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
    // Declare primary template for index range builder
    template <unsigned MIN, unsigned N, unsigned... Is>
    struct range_builder;

    // Base step
    template <unsigned MIN, unsigned... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    // Induction step
    template <unsigned MIN, unsigned N, unsigned... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    {
    };
}

// Meta-function that returns a [MIN, MAX) index range
template<unsigned MIN, unsigned MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;

//===============================================================================
// CLASSES AND FUNCTIONS FOR REALIZING LOOPS ON ARGUMENT PACKS

// Implementation inspired by @jogojapan's answer to this question:
// http://stackoverflow.com/questions/14089637/return-several-arguments-for-another-function-by-a-single-function

// Collects internal details for implementing functor invocation
namespace detail
{
    // Functor invocation is realized through variadic inheritance.
    // The constructor of each base class invokes an input functor.
    // An functor invoker for an argument pack has one base class
    // for each argument in the pack

    // Realizes the invocation of the functor for one parameter
    template<unsigned I, typename T>
    struct invoker_base
    {
        template<typename F, typename U>
        invoker_base(F&& f, U&& u) { f(u); }
    };

    // Necessary because a class cannot inherit the same class twice
    template<unsigned I, typename T>
    struct indexed_type
    {
        static const unsigned int index = I;
        using type = T;
    };

    // The functor invoker: inherits from a list of base classes.
    // The constructor of each of these classes invokes the input
    // functor with one of the arguments in the pack.
    template<typename... Ts>
    struct invoker : public invoker_base<Ts::index, typename Ts::type>...
    {
        template<typename F, typename... Us>
        invoker(F&& f, Us&&... args)
            :
            invoker_base<Ts::index, typename Ts::type>(std::forward<F>(f), std::forward<Us>(args))...
        {
        }
    };
}

// The functor provided in the first argument is invoked for each
// argument in the pack whose index is contained in the index list
// specified in the second argument
template<typename F, unsigned... Is, typename... Ts>
void for_each_in_arg_pack_subset(F&& f, index_list<Is...> const& i, Ts&&... args)
{
    // Constructors of invoker's sub-objects will invoke the functor.
    // Note that argument types must be paired with numbers because the
    // implementation is based on inheritance, and one class cannot
    // inherit the same base class twice.
    detail::invoker<detail::indexed_type<Is, typename nth_type_of<Is, Ts...>::type>...> invoker(
        f,
        (nth_value_of<Is>(std::forward<Ts>(args)...))...
        );
}

// The functor provided in the first argument is invoked for each
// argument in the pack
template<typename F, typename... Ts>
void for_each_in_arg_pack(F&& f, Ts&&... args)
{
    for_each_in_arg_pack_subset(f, index_range<0, sizeof...(Ts)>(), std::forward<Ts>(args)...);
}

// The functor provided in the first argument is given in input the
// arguments in whose index is contained in the index list specified
// as the second argument.
template<typename F, unsigned... Is, typename... Ts>
void forward_subpack(F&& f, index_list<Is...> const& i, Ts&&... args)
{
    f((nth_value_of<Is>(std::forward<Ts>(args)...))...);
}

// The functor provided in the first argument is given in input all the
// arguments in the pack.
template<typename F, typename... Ts>
void forward_pack(F&& f, Ts&&... args)
{
    f(std::forward<Ts>(args)...);
}

ЗАКЛЮЧЕНИЕ

Конечно, даже несмотря на то, что я дал свой собственный ответ на этот вопрос (и на самом деле из-за этого факта), мне любопытно узнать, существуют ли альтернативные или лучшие решения, которые я пропустил - помимо упомянутых в разделе вопроса "Родственные работы".

person Andy Prowl    schedule 10.01.2013
comment
Это честно заслуживает большего количества голосов, чем имеет. Мне понравилась идея static if и static for, так как они сделали бы метапрограммирование намного более легким для чтения, записи и понимания, но я бы совсем не возражал, если бы они были реализованы в стандартной библиотеке. - person chris; 29.05.2013
comment
@chris: На самом деле, большая часть этой функциональности может быть реализована с помощью Boost.Fusion, сначала преобразовав пакет аргументов в кортеж (к тому времени, когда я написал это, я не знал, что компилятор оптимизирует кортеж). Но спасибо за вашу признательность :) - person Andy Prowl; 29.05.2013
comment
Хм, я не знал, что Boost Fusion может это сделать. Честно говоря, у меня очень мало опыта работы с Boost (я узнал об этом только после того, как был оснащен C ++ 11), но библиотека Fusion и многие другие вызвали у меня интерес. - person chris; 29.05.2013
comment
Я знаю, что этот ответ был написан три года назад, но я надеюсь, что вы ответите мне: почему вы не написали last_type_of с реализацией O (1)? Кто-нибудь пишет функцию с параметром 1024 (т.е. я не говорю о проблемах с шаблоном), но вы можете сократить время компиляции. Спасибо. - person Manu343726; 31.07.2013
comment
@ Manu343726: Это было всего 6 месяцев назад, а не 3 года: D Меня не волновала сложность, потому что все это делается во время компиляции, поэтому вычислительная сложность обычно не вызывает беспокойства. Я просто написал простейшую реализацию, которая пришла мне в голову :) - person Andy Prowl; 31.07.2013
comment
О, это правда: D. Если вы должны написать эффективную реализацию, что бы вы реализовали? Обычно я делаю это, используя специализацию и пропуская вариативный пакет через список типов. - person Manu343726; 31.07.2013
comment
Привет! Я анализировал ваш код и не мог понять кое-что относительно базового шага nth_value_of: почему я не могу объявить тип возвращаемого значения функции как typename std::enable_if<(I == 0),T>::type? Зачем вам там нужен форвард? Спасибо за помощь! - person Kolyunya; 23.01.2014
comment
@Kolyunya: Потому что это сделало бы возвращаемый тип функции равным T, когда аргумент t привязан к rvalue, в то время как я хочу, чтобы возвращался T&& - чтобы обеспечить идеальную пересылку, без копирования. Например, если rvalue типа int передается в качестве первого аргумента, T не будет выводиться как int&&, а будет просто int. Я использую std::forward для возврата int&& при передаче rvalue и int& при передаче lvalue. Я не могу расширять lvalues, rvalues ​​и perfect forwarding в этом коротком комментарии, но если объяснения недостаточно, попробуйте сначала прочитать об этих темах. Ваше здоровье :) - person Andy Prowl; 24.01.2014
comment
И снова здравствуйте! Я продолжал анализировать ваш код и не мог понять что-то о типе возвращаемого значения шага индукции nth_value_of. Зачем вам оборачивать std::declval из nth_type_of в std::forward, если std::declval не возвращает ничего, кроме ссылки на rvalue. Не излишне ли в этом случае передняя обертка? Также стоит ли заменить содержимое decltype рекурсивным вызовом nth_value_of, как в теле функции? Большое спасибо за Вашу помощь! - person Kolyunya; 28.01.2014
comment
@Kolyunya: std::declval не всегда возвращает ссылку rvalue: из-за сворачивания ссылок, если аргумент шаблона является ссылочным типом lvalue, он вернет lvalue (ссылку). Этот механизм необходим для идеальной пересылки возвращаемых значений - я не помню точно всех деталей, я написал это около года назад;) В любом случае, вы можете попробовать его изменить, скомпилировать и посмотреть, что произойдет :) Ура - person Andy Prowl; 28.01.2014
comment
Раньше я думал, что declval возвращает только rvalue-ссылки после прочтения cplusplus.com/reference/utility/ declval Но оказывается, что это не всегда верно из-за ru. cppreference.com/w/cpp/utility/declval Еще раз спасибо за вашу помощь! - person Kolyunya; 28.01.2014
comment
@Kolyunya: Что ж, технически то, что написано на cplusplus.com, правильно (однако будьте осторожны, этот сайт в прошлом был известен своим неточным содержанием), потому что верно, что std::declval возвращает ссылку rvalue на тип T. Однако, если T является сам является ссылочным типом, тогда срабатывает свертывание ссылок, поэтому вы должны это учитывать;) - person Andy Prowl; 28.01.2014
comment
Это то, что я называю УДИВИТЕЛЬНЫМ !!! Вы должны отправить его в ускорение. Назовите это boost :: parameter_pack или что-то в этом роде. - person Yury Korobkin; 07.02.2014
comment
@ user3019690: Спасибо, я рада, что вам понравилось! К сожалению для меня (и, к счастью для всего мира), уже существует Boost.Fusion, который позволяет делать почти все это и многое другое;) - person Andy Prowl; 07.02.2014
comment
@AndyProwl: Какова цель forward_pack()? Какую проблему он решает, а std::forward() - нет? - person einpoklum; 17.07.2015
comment
@einpoklum: Это было давно, и я не помню, зачем я это добавил. Когда я смотрю на это сейчас, это действительно кажется ненужным, но кто знает: D - person Andy Prowl; 17.07.2015
comment
@AndyProwl 1. Есть ли в вашем примере способ передать дополнительные параметры в print? 2 продолжение, что, если тип дополнительного параметра является производным от T? - person qqibrow; 31.08.2015

Разрешите опубликовать этот код на основе обсуждения:

#include <initializer_list>
#define EXPAND(EXPR) std::initializer_list<int>{((EXPR),0)...}

// Example of use:
#include <iostream>
#include <utility>

void print(int i){std::cout << "int: " << i << '\n';}
int print(double d){std::cout << "double: " << d << '\n';return 2;}

template<class...T> void f(T&&...args){
  EXPAND(print(std::forward<T>(args)));
}

int main(){
  f();
  f(1,2.,3);
}

Я проверил сгенерированный код с g++ -std=c++11 -O1, а main содержит только 3 вызова print, помощников расширения нет.

person Marc Glisse    schedule 10.01.2013
comment
Я ценю, что вы нашли время, чтобы попытаться ответить на мой вопрос. На данный момент у меня есть один комментарий к вашему решению (не значит, что его нельзя исправить): оно не использует идеальную пересылку. С int и doubles это не имеет большого значения, но с UDT это означает, что он будет генерировать копии и перемещаться. И вы не можете изменить EXPAND(print(args)) на EXPAND (print (forward ‹T› (args))), потому что препроцессор макроса будет кричать что-то невежливое. - person Andy Prowl; 11.01.2013
comment
Я просто добавил вперед ‹T› (спасибо, забыл добавить), а препроцессор макросов вообще не жаловался ... - person Marc Glisse; 11.01.2013
comment
Вы правы, я почему-то предположил, что препроцессору не нравятся угловые скобки. не знаю почему. поэтому я предполагаю, что это действительная альтернатива моему подходу для более простых случаев, когда вы хотите перебрать все элементы диапазона. у него также есть то преимущество, что он позволяет вызываемой функции быть шаблоном функции, а не просто функтором, как того требует мое решение. - person Andy Prowl; 11.01.2013
comment
Я собираюсь принять свой ответ, потому что считаю, что он отражает более общий дух вопроса (как выполнять общие операции с пакетами аргументов). Ваш метод очень интересен, потому что он компактен и не требует функторов, но он применим ко всему списку параметров и не позволяет легко разделить пакет или выбрать подпакет, через который будет выполняться итерация. Определенно +1 хотя - person Andy Prowl; 12.01.2013
comment
Ok. Обратите внимание, что два подхода можно легко комбинировать. Вперед один раз, чтобы сделать index_range доступным, а затем использовать EXPAND для выполнения любого кода, включающего как объект, так и его индекс (он не возражает против одновременного расширения двух пакетов). - person Marc Glisse; 12.01.2013
comment
Совершенно верно: фактически, я интегрировал ваше определение макроса в свою библиотеку. Большое спасибо за участие. - person Andy Prowl; 12.01.2013

Использование решения enumerate (как Python).

Использование:

void fun(int i, size_t index, size_t size) {
    if (index != 0) {
        std::cout << ", ";
    }

    std::cout << i;

    if (index == size - 1) {
        std::cout << "\n";
    }
} // fun

enumerate(fun, 2, 3, 4);

// Expected output: "2, 3, 4\n"
// check it at: http://liveworkspace.org/code/1cydbw$4

Код:

// Fun: expects a callable of 3 parameters: Arg, size_t, size_t
// Arg: forwarded argument
// size_t: index of current argument
// size_t: number of arguments
template <typename Fun, typename... Args, size_t... Is>
void enumerate_impl(Fun&& fun, index_list<Is...>, Args&&... args) {
    std::initializer_list<int> _{
        (fun(std::forward<Args>(args), Is, sizeof...(Is)), 0)...
    };
    (void)_; // placate compiler, only the side-effects interest us
}

template <typename Fun, typename... Args>
void enumerate(Fun&& fun, Args&&... args) {
    enumerate_impl(fun,
                   index_range<0, sizeof...(args)>(),
                   std::forward<Args>(args)...);
}

Построитель диапазона (украденный из вашего решения):

// The structure that encapsulates index lists
template <size_t... Is>
struct index_list
{
};

// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
    // Declare primary template for index range builder
    template <size_t MIN, size_t N, size_t... Is>
    struct range_builder;

    // Base step
    template <size_t MIN, size_t... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    // Induction step
    template <size_t MIN, size_t N, size_t... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    {
    };
}

// Meta-function that returns a [MIN, MAX) index range
template<size_t MIN, size_t MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
person Matthieu M.    schedule 13.02.2013
comment
@AndyProwl: Должен признать, что за последние несколько лет я не раз удивлялся элегантности ряда стандартных функций Python. Пример enumerate - это то, чего мне очень не хватает, хотя я полагаю, что обычно его было бы больше for (auto p: enumerate(container)), и я не уверен, что две версии (итерация контейнера и итерация кортежа) будут хорошо жить вместе :) - person Matthieu M.; 13.02.2013
comment
Я должен со стыдом признаться в моем безжалостном незнании Python :-) Что ж, кажется, мне стоит начать его изучать. - person Andy Prowl; 13.02.2013
comment
@AndyProwl: функция enumerate и модуль itertools - хорошие отправные точки :) - person Matthieu M.; 13.02.2013

В нотации ... есть несколько интересных опций, например:

template<typename T>
int print(const T& x) {
  std::cout << "<" << x << ">";
  return 0;
}

void pass(...) {}

template<typename... TS>
void printall(TS... ts){
  pass(print(ts)...);
}

К сожалению, я не знаю способа принудительно установить порядок, в котором вызываются функции печати (в обратном порядке, в моем компиляторе). Обратите внимание, что print должен что-то возвращать.

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

person dspeyer    schedule 15.01.2013
comment
См. gitorious.org/redistd/redistd/blobs/master/include / redi / для одного из способов принудительного упорядочивания, используя стандартный трюк функционального программирования, заключающийся в печати заголовка пакета параметров с последующей рекурсивной обработкой хвоста. - person Jonathan Wakely; 13.02.2013

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

Использование с использованием лямбда-выражения -

static_for_each()(
    [](std::string const& str)
    {
        std::cout << str << std::endl;
    }, "Hello, ", "Lambda!");

Обратной стороной использования лямбды является то, что параметры должны быть того же типа, что и объявлены в списке параметров лямбда. Это означает, что он будет работать только с одним типом. Если вы хотите использовать шаблонную функцию, вы можете использовать следующий пример.

Использование с использованием функтора структуры-оболочки -

struct print_wrapper
{
    template <typename T>
    void operator()(T&& str)
    {
        std::cout << str << " ";
    }
};

// 
// A little test object we can use.
struct test_object
{
    test_object() : str("I'm a test object!") {}
    std::string str;
};

std::ostream& operator<<(std::ostream& os, test_object t)
{
    os << t.str;
    return os;
}

//
// prints: "Hello, Functor! 1 2 I'm a test object!"
static_for_each()(print_wrapper(), "Hello,", "Functor!", 1, 2.0f, test_object());

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

template <typename T, typename... Args>
void call(T f, Args... args)
{
    static_for_each()(f, args...);
}

call(print_wrapper(), "Hello", "Call", "Wrapper!");

Вот реализация -

// 
// Statically iterate over a parameter pack 
// and call a functor passing each argument.
struct static_for_each
{
private:
    // 
    // Get the parameter pack argument at index i.
    template <size_t i, typename... Args>
    static auto get_arg(Args&&... as) 
    -> decltype(std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...)))
    {
        return std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...));
    }

    //
    // Recursive template for iterating over 
    // parameter pack and calling the functor.
    template <size_t Start, size_t End>
    struct internal_static_for
    {
        template <typename Functor, typename... Ts>
        void operator()(Functor f, Ts&&... args)
        {
            f(get_arg<Start>(args...));
            internal_static_for<Start + 1, End>()(f, args...);
        }
    };

    //
    // Specialize the template to end the recursion.
    template <size_t End>
    struct internal_static_for<End, End>
    {
        template <typename Functor, typename... Ts>
        void operator()(Functor f, Ts&&... args){}
    };

public:
    // 
    // Publically exposed operator()(). 
    // Handles template recursion over parameter pack.
    // Takes the functor to be executed and a parameter 
    // pack of arguments to pass to the functor, one at a time.
    template<typename Functor, typename... Ts>
    void operator()(Functor f, Ts&&... args)
    {
        // 
        // Statically iterate over parameter
        // pack from the first argument to the
        // last, calling functor f with each 
        // argument in the parameter pack.
        internal_static_for<0u, sizeof...(Ts)>()(f, args...);
    }
};

Надеюсь, люди сочтут это полезным :-)

person pje    schedule 07.11.2014