如何通过可变参数模板函数的异构参数包进行generics计算?
前提:
在玩了各种各样的模板之后,我意识到,实现任何超出微不足道的元编程任务的东西都将变得非常麻烦。 特别是,我发现自己希望有一种方法来执行generics操作,比如迭代 , 分割 , 循环等 std::for_each
方式,等等。
在看了C ++和Beyond 2012 的Andrei Alexandrescu关于static if
从C语言中借用一个构造)的愿望之后,我感觉到某种static for
也会变得方便 – 我感觉更多这些static
结构可以带来好处。
所以我开始想知道是否有一种方法来实现类似于 variadic模板函数( 伪代码 )的参数包:
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)); } }
或者像这样一个更具performance力的成语:
template<typename... Ts> void foo(Ts&&... args) { static for_each (auto&& x : args) { foo(x); } }
相关工作:
我在网上做了一些search,发现确实存在一些东西 :
- 这个链接描述了如何将一个参数包转换成一个Boost.MPL向量,但是这个向量仅仅是一半(如果不是更less的话)。
- SO上的这个问题似乎要求一个类似的,稍有关联的元编程function(将一个参数包分成两半) – 实际上,有几个关于SO的问题似乎与这个问题有关,但是没有一个答案已经阅读解决它令人满意的恕我直言;
- Boost.Fusion定义了将参数包转换成元组的algorithm,但是我更喜欢:
- 不要创build不必要的临时对象来保存可以(而且应该)完全转发给一些通用algorithm的参数;
- 有一个小型的,自包含的库来做到这一点,而Boost.Fusion可能包括更多的东西比解决这个问题所需要的东西。
题:
有没有一种相对简单的方法,可能通过一些模板元编程来实现我所期望的,而不会受到现有方法的限制?
由于我对所发现的东西并不满意,所以我试图自己找出一个解决scheme,最后编写了一个小型库 ,允许在参数包中进行通用操作。 我的解决scheme具有以下function:
- 允许遍历参数包的全部或部分元素,可能通过在包中计算它们的索引来指定;
- 允许将参数包的计算部分转发给可变函数;
- 只需要包含一个相对较短的头文件;
- 大量使用完美的转发function以允许大量内联,并避免不必要的复制/移动,以实现最低的性能损失;
- 迭代algorithm的内部实现依赖于空基类优化来最小化内存消耗;
- 这很容易(相对而言,考虑到它的模板元编程)来扩展和适应。
我将首先展示图书馆可以做些什么 , 然后发布它的实施 。
用例
下面是一个如何使用for_each_in_arg_pack()
函数来遍历一个包的所有参数并将每个parameter passing给客户端提供的函数的例子(当然,如果参数必须有一个generics调用操作符包包含不同types的值):
// 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()
algorithm允许做的事情:
// 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; }
另一方面,如果参数包是同质的(即所有的参数都是相同的types),那么下面的公式可能更可取。 is_homogeneous_pack<>
元函数允许确定参数包中的所有types是否是同类的,主要用于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; }
最后,因为lambdas只是函子的语法糖 ,所以也可以和上面的algorithm结合使用。 然而,直到genericslambdas将被C ++支持,这只适用于同类参数包。 以下示例还显示了homogeneous-type<>
元函数的用法,该函数返回同构包中所有参数的types:
// ... 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)...); }
结论
当然,即使我提出了自己对这个问题的回答(实际上是因为这个事实),我很想知道是否存在我所错过的替代或更好的解决scheme – 除了“相关工程”部分的问题。
让我发表这个代码,根据讨论:
#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
调用,没有扩展帮助的踪迹。
使用枚举解决scheme(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)...); }
范围build造者(从你的解决scheme中获得):
// 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;
…符号确实有一些有趣的选项,如:
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)...); }
不幸的是,我不知道如何执行打印函数的调用顺序(在我的编译器中是反向的)。 请注意,打印需要返回一些东西。
如果你不关心订单,这个技巧会很有用。
在阅读了其他一些post并修改了一段时间后,我想出了以下内容(与上面有些相似,但实现方式稍有不同)。 我使用Visual Studio 2013编译器编写了此代码。
使用lambdaexpression式的用法 –
static_for_each()( [](std::string const& str) { std::cout << str << std::endl; }, "Hello, ", "Lambda!");
使用lambda的缺点是参数必须是在lambda参数列表中声明的相同types。 这意味着它只能使用一种types。 如果要使用模板化function,可以使用下一个示例。
使用struct wrapper函子的用法 –
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());
这允许你使用函数来传递任何你想要的types和对它们进行操作。 我发现这很干净,很适合我想要的东西。 你也可以像这样使用它的函数参数包 –
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...); } };
希望人们觉得这有用:-)