Як зробити загальні обчислення за неоднорідними пакетами аргументів варіадичної функції шаблону?


77

ПРОМІСЛЯ:

Трохи погравши з варіативними шаблонами, я зрозумів, що досягнення всього, що трохи виходить за межі тривіальних завдань метапрограмування, незабаром стає досить громіздким. Зокрема, я виявив , бажаючи таким чином , щоб виконувати загальні операції над аргументом пакета , такі як ітерації , розкол , петлі в std::for_each-like моди, і так далі.

Подивившись цю лекцію Андрія Александреску з C ++ та Beyond 2012 про бажаність використання static ifC ++ (конструкція, запозичена з мови програмування 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, які, здається, пов’язані з цією проблемою, але жодної відповіді I прочитали вирішує це задовільно ІМХО;
  • Boost.Fusion визначає алгоритми перетворення пакета аргументів у кортеж , але я вважаю за краще:
    1. не створювати непотрібних тимчасов для зберігання аргументів, які можуть (і повинні бути) ідеально перенаправлені до деяких загальних алгоритмів;
    2. мати невелику, автономну бібліотеку для цього, тоді як Boost.Fusion, швидше за все, включатиме в себе більше матеріалів, ніж потрібно для вирішення цієї проблеми.

ПИТАННЯ:

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


Якщо foo щось повернув, ви можете просто написати eat (foo (args) ...), де eat - це функція, яка нічого не робить зі своїми аргументами. Потрібна певна настройка для функцій, які повертають void, або якщо ви хочете вказати порядок виконання (це вже обговорювалось у usenet, можливо comp.lang.c ++. Модерується, хоча наразі я його не можу знайти). Було обговорено дозволитиfoo(args);...
Марк Глісс

@MarcGlisse: Ви маєте думку, і справді я спробував це. Проблема з цим рішенням полягає в тому, що C ++ не гарантує жодного порядку для оцінки аргументів функції, що часто є бажаним при ітерації (крім того, що функції повинні повертати значення, навіть коли вони не зобов'язані, але це незначно).
Andy Prowl,

Я думаю, є варіант, де порядок оцінки аргументів вказаний, можливо всередині {} (список ініціалізатора) вони знаходяться. Що стосується типу повернення, ви, мабуть, можете зробити (foo (args), 0) ... або інший трюк.
Marc Glisse

@MarcGlisse: це може бути так, будь ласка, спробуйте спробувати та вдосконалити мою бібліотеку, якщо хочете. я був би радий, якби це можна було зробити краще. відверто кажучи, я думаю, що моє рішення не створює жодних накладних витрат, а також не створює жодних обмежень на стороні клієнта, що для моїх цілей добре. але це не означає, що це ідеально, звичайно
Енді Проул

1
FWIW, раніше я вже перевіряв, що і GCC, і VC ++ повністю оптимізують кортежі посилань у збірках випусків - ідентичний кодеген, що не використовує кортежі (тестування з Boost.Fusion).
ildjarn

Відповіді:


65

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

  • Дозволяє перебирати всі або деякі елементи пакета аргументів, можливо, вказаних шляхом обчислення їх індексів на пакеті;
  • Дозволяє переадресовувати обчислювані частини пакета аргументів до варіативних функторів;
  • Потрібно включити лише один порівняно короткий заголовочний файл;
  • Широко використовує ідеальну переадресацію, щоб забезпечити важкі вкладання та уникає зайвих копій / переміщень, щоб забезпечити мінімальну втрату продуктивності;
  • Внутрішня реалізація ітераційних алгоритмів покладається на Оптимізацію порожнього базового класу для мінімізації споживання пам'яті;
  • Його легко (відносно, враховуючи метапрограмування шаблону) розширити та адаптувати.

Спочатку я покажу, що можна зробити з бібліотекою, а потім опублікую її реалізацію .

ВИКОРИСТАННЯ СПРАВ

Ось приклад того, як 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)...);
}

ВИСНОВОК

Звичайно, незважаючи на те, що я дав власну відповідь на це запитання (і насправді через цей факт), мені цікаво почути, чи існують альтернативні чи кращі рішення, яких я пропустив - крім тих, що згадані в розділі "Пов'язані роботи" питання.


4
Це чесно заслуговує на більшу підтримку, ніж є. Мені сподобалась ідея, static ifі static for, як на мене, вони значно полегшили б метапрограмування для читання, написання та розуміння, але я б не заперечував взагалі, якби вони були реалізовані в стандартній бібліотеці.
chris

2
@chris: Насправді більшу частину цієї функціональності можна реалізувати за допомогою Boost.Fusion, спершу перетворивши пакет аргументів у кортеж (на той час, коли я це писав, я не знав, що компілятор оптимізував би кортеж). Але дякую за вдячність :)
Енді Проул,

Хм, я не знав, що Boost Fusion може це зробити. Чесно кажучи, у мене дуже мало досвіду роботи з Boost all around (про це я дізнався лише після оснащення C ++ 11), але бібліотека Fusion та ще декілька інших викликали мій інтерес.
Кріс

Я знаю, що ця відповідь була написана три роки тому, але, сподіваюся, ви зможете відповісти мені: чому ви не написали last_type_ofз реалізацією O (1)? Будь-хто пише функцію 1024 param (тобто я не кажу про проблеми з шаблоном-дефіцитом), але ви можете скоротити час компіляції. Дякую.
Manu343726

@ Manu343726: Це було лише 6 місяців тому, а не 3 роки: DI не піклувався про складність, оскільки все це робиться під час компіляції, тому обчислювальна складність, як правило, не викликає занепокоєння. Я щойно написав найпростішу реалізацію, яка мені
спала

10

Дозвольте мені розмістити цей код на основі обговорення:

#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, від помічників розширення немає сліду.


Я ціную, що ви знайшли час, щоб спробувати відповісти на моє запитання. На даний момент у мене є один коментар до вашого рішення (не означає, що його неможливо виправити): він не використовує ідеальну переадресацію. З intі doubles це не має великого значення, але для UDT це означає, що він буде генерувати копії та переміщення. І ви не можете перейти EXPAND(print(args))на EXPAND (print (forward <T> (args))), оскільки макропроцесор обробляє щось неввічливе.
Енді Проул

Я просто додав вперед <T> (дякую, я забув його додати), і макропроцесор взагалі не скаржився ...
Marc Glisse

Ви праві, чомусь я припустив, що препроцесор не любить кутові дужки. не впевнений чому. тому я вважаю, що це вірна альтернатива моєму підходу для простих випадків, коли ви хочете повторити всі елементи діапазону. він також має ту перевагу, що дозволяє викликаній функції бути шаблоном функції, а не просто функтором, як вимагає моє рішення.
Енді Проул,

Я збираюся прийняти власну відповідь, тому що я вважаю, що саме вона охоплює загальний дух питання (як виконувати загальні операції над пакетами аргументів). Ваша техніка дуже цікава, оскільки вона компактна і не потребує функторів, але вона застосовується до всього списку параметрів і не дозволяє легко розділити пакет або вибрати підпакет, за допомогою якого слід виконати ітерацію. Безумовно, +1
Енді Проул

Гаразд. Зверніть увагу, що два підходи можна легко поєднати. Вперед один раз, щоб зробити діапазон_індексу доступним, а потім використовуйте EXPAND, щоб виконати будь-який код, що включає як об'єкт, так і його індекс (він не проти розширити 2 пакети одночасно).
Marc Glisse

5

Використання рішення переліку (ala 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;

@AndyProwl: Я повинен визнати, що за останні кілька років мене не раз дивувала елегантність ряду "стандартних" функцій Python. enumerateПриклад то , що я дуже сумую, хоча я припускаю , як правило , було б for (auto p: enumerate(container))і я не впевнений , що дві версії (контейнер ітерації і кортеж ітерації) буде жити добре разом :)
Матьє М.

Я повинен ганебно зізнатися у своєму величезному незнанні Python тут :-) Ну, здається, мені слід почати його вивчати
Енді Проул

@AndyProwl: функція перерахування та модулі itertools - хороші відправні точки :)
Matthieu M.

0

Позначення ... має кілька цікавих варіантів, таких як:

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

На жаль, я не знаю жодного способу встановити порядок, у якому викликаються функції друку (зворотний, на моєму компіляторі). Зверніть увагу, що друк повинен щось повернути.

Цей фокус може стати в нагоді, якщо вам не байдуже порядок.


Дивіться gitorious.org/redistd/redistd/blobs/master/include/redi/… про один із способів примусового впорядкування, використовуючи стандартний функціональний трюк програмування друку голови пакета параметрів, а потім рекурсивної обробки хвоста
Джонатан Вакелі,

0

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

Використання з використанням лямбда-виразу -

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

Недоліком при використанні лямбда-параметри є те, що параметри повинні бути одного типу, заявленого в списку параметрів лямбда-сигналу. Це означає, що він буде працювати лише з одним типом. Якщо ви хочете використовувати шаблонну функцію, ви можете використати наступний приклад.

Використання за допомогою функтора обгортки struct -

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

Сподіваюся, людям це буде корисно :-)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.