Как сделать общие вычисления над гетерогенными пакетами аргументов функции вариационного шаблона?
помещения:
немного поиграв с вариативными шаблонами, я понял, что достижение всего, что выходит за рамки тривиальных задач метапрограммирования, вскоре становится довольно громоздким. В частности, я обнаружил, что желаю способ выполнить универсальные операции над пакетом аргументов например iterate,сплит,цикл на 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);
}
}
СВЯЗАННАЯ РАБОТА:
Я сделал некоторый поиск в интернете и узнал, что что-то действительно существует:
- этой ссылке описывает, как преобразовать пакет параметров в толчок.MPL вектор, но это только половина пути (если не меньше) к цели;
- этот вопрос о SO, похоже, требует аналогичной и слегка связанной функции метапрограммирования (разбиение пакета аргументов на две половины) - на самом деле, есть несколько вопросов о SO, которые, похоже, связаны с этой проблемой, но ни один из ответ я прочитал решает его удовлетворительно ИМХО;
-
импульс.Fusion определяет алгоритмы для преобразования пакета аргументов в кортежа!--17-->, но я бы предпочел:
- не создавать ненужных и временных для хранения аргументов, которые могут (и должны быть) отлично перенаправлены на некоторые общие алгоритмы;
- Есть маленький, самодостаточный библиотека, чтобы сделать это, в то время как повышение.Слияние, вероятно, будет включать в себя способ больше вещей, чем необходимо для решения этой проблемы.
вопрос:
есть ли относительно простой способ, возможно, с помощью некоторого шаблона мета-программирования, чтобы достичь того, что я ищу, не подвергаясь ограничениям существующих подходов?
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)...); }
The
// 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 ); }
иногда, можно просто захотеть вперед часть из пакета аргумент к некоторым другим переменным числом аргументов функтора вместо того, чтобы изменять его элементы и передать каждый из них индивидуально к вариативной функтор. Это позволяет:
// 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)...); }
вывод
конечно, хотя я дал свой собственный ответ на этот вопрос (и на самом деле из-за этот факт), мне любопытно услышать, существуют ли альтернативные или лучшие решения, которые я пропустил-кроме тех, которые упомянуты в "связанном Работает " раздел вопроса.
позвольте мне опубликовать этот код, основанный на обсуждении:
#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 вызова
С помощью перечислить решение (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
код:
// 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;
The ... нотация имеет некоторые интересные варианты, такие как:
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)...); }
к сожалению, я не знаю никакого способа обеспечить порядок, в котором вызываются функции печати (обратный, на моем компиляторе). Обратите внимание, что печать должна что-то вернуть.
этот трюк может быть полезен, если вы не заботиться о порядке.
прочитав несколько других постов и повозившись некоторое время я придумал следующее (чем-то похожее на вышеописанное, но реализация немного отличается). Я написал это с помощью компилятора 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...); } };
надеюсь, что люди найдут это полезным : -)