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

ПРЕДСТАВКА:

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

След като изгледахте тази лекция от Андрей Александреску от C++ and Beyond 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, които изглежда са свързани с този проблем, но никой от отговорите, които прочетох, не го решава задоволително IMHO;
  • 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

Имам UpdateCommand за решетка, както следва:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:KruSQL %>"  
   UpdateCommand="UPDATE [dbo].[Microbiology] SET RoomNum=@RoomNum, CollDate=@CollDate, WaterFixure=@WaterFixure Where ID=@ID">
<UpdateParameters>
   <asp:Parameter Name="RoomNum" Type="String" />
   <asp:Parameter Name="WaterFixure" Type="String" />            
   <asp:Parameter Name="CollDate" Type="DateTime" />             
</UpdateParameters>  

Когато потребителят въведе дата, трябва да я конвертирам в UTC. Има ли начин да направите това от UpdateCommand - добавете функция. Не се получи, когато го пробвах.

  -  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++ напълно оптимизират кортежи от препратки в компилации на версиите – идентичен codegen с неизползване на кортежи (тестване с Boost.Fusion).   -  person ildjarn    schedule 16.01.2013
comment
@ildjarn: това е полезна информация. това прави работата ми по-малко полезна, но все пак полезна информация. Благодаря ти   -  person Andy Prowl    schedule 16.01.2013
comment
Забележка: в python има функция, която итерира върху итериран и връща както елемента с неговия индекс, тя се нарича enumerate. Бих обмислил промяна на името от for_each, което (в стандартната библиотека) е без индекс, на нещо друго... като изброяване и функторът да приема както елемент, така и индекс (и може би общия размер като добре ?) :)   -  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)


Тъй като не бях доволен от това, което открих, се опитах сам да намеря решение и в крайна сметка написах малка библиотека, която позволява формулиране на общи операции върху пакети аргументи. Моето решение има следните характеристики:

  • Позволява повторение на всички или някои елементи от пакет аргументи, евентуално посочени чрез изчисляване на техните индекси в пакета;
  • Позволява препращане на изчислени части от пакет аргументи към променливи функтори;
  • Изисква само включване на един относително кратък заглавен файл;
  • Използва широко перфектно препращане, за да позволи тежко вграждане и избягва ненужни копия/премествания, за да позволи минимална загуба на производителност;
  • Вътрешното внедряване на итерационните алгоритми разчита на Empty Base Class Optimization за минимизиране на потреблението на памет;
  • Лесно е (относително, като се има предвид метапрограмирането на шаблон) за разширяване и адаптиране.

Първо ще покажа какво може да се направи с библиотеката, след което ще публикувам нейното внедряване.

СЛУЧАИ НА УПОТРЕБА

Ето пример за това как функцията 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 param (т.е. не говоря за проблеми с deph шаблона), но можете да намалите времето за компилиране. Благодаря. - 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 ​​и перфектното пренасочване в този кратък коментар, но ако обяснението не е достатъчно, опитайте първо да прочетете за тези теми. Наздраве :) - 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 (препратка). Тази машина е необходима за перфектно препращане на върнатите стойности - не си спомням точно всички подробности, написах това нещо преди около 1 година ;) Както и да е, можете да опитате да го промените, компилирате и да видите какво ще се случи :) Наздраве - person Andy Prowl; 28.01.2014
comment
Мислех, че declval връща само rvalue-references след прочитане на cplusplus.com/reference/utility/ declval Но излиза, че това не винаги е вярно поради en. 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
Добре. Имайте предвид, че двата подхода могат лесно да се комбинират. Препратете веднъж, за да направите index_range достъпен, и след това използвайте EXPAND, за да изпълните какъвто и да е код, включващ както обекта, така и неговия индекс (няма нищо против разширяването на 2 пакета едновременно). - person Marc Glisse; 12.01.2013
comment
Абсолютно: всъщност аз интегрирах вашата макро дефиниция в моята библиотека. Благодаря много за приноса. - person Andy Prowl; 12.01.2013

Използване на решение за изброяване (ала 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