如何对可变参数模板函数的异构参数包进行通用计算?

Posted

技术标签:

【中文标题】如何对可变参数模板函数的异构参数包进行通用计算?【英文标题】:How to make generic computations over heterogeneous argument packs of a variadic template function? 【发布时间】:2012-12-25 00:47:21 【问题描述】:

前提:

在尝试了一些可变参数模板之后,我意识到,完成任何超出琐碎元编程任务的事情很快就会变得非常麻烦。特别是,我发现自己希望找到一种方法来执行对参数包的通用操作,例如 iteratesplitloop 以类似std::for_each 的方式,等等。

在观看了来自 C++ 和 Beyond 2012 的 this lecture by Andrei Alexandrescu 之后,关于 static if 对 C++ 的可取性(从 D Programming Language 借用的构造)我觉得某种 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);
    

相关工作:

我在网上搜索了一下,发现确实存在某物

This link 描述了如何将参数包转换为 Boost.MPL 向量,但这只是实现目标的一半(如果不是更少); this question on SO 似乎需要一个类似且略微相关的元编程功能(将参数包分成两半) - 实际上,有几个关于 SO 的问题似乎与这个问题有关,但我没有答案恕我直言,阅读令人满意地解决了它; Boost.Fusion 定义了将参数包转换为 tuple 的算法,但我更喜欢:
    不要创建不必要的临时变量来保存可以(并且应该)完美地转发给某些通用算法的参数; 有一个小型、独立的库来执行此操作,而 Boost.Fusion 可能包含的内容远远超过解决此问题所需的内容。

问题:

有没有一种相对简单的方法,可能通过一些模板元编程来实现我正在寻找的东西,而不会受到现有方法的限制?

【问题讨论】:

如果 foo 返回了一些东西,你可以写 eat(foo(args)...) 其中 eat 是一个函数,它的参数什么都不做。需要对返回 void 的函数进行一些调整,或者如果你想指定执行顺序(它已经在 usenet 上讨论过,可能是 comp.lang.c++.moderated,虽然我现在找不到它)。讨论过允许foo(args);... @MarcGlisse:你说得有道理,我确实试过了。该解决方案的问题在于 C++ 不保证函数参数评估的任何顺序,这是迭代时经常需要的(除了函数必须返回值的事实,即使它们不需要,但那是次要的)。 我认为有一个变体,其中指定了参数的评估顺序,可能在 (初始化列表)内。至于返回类型,你可能可以做 (foo(args),0)... 或其他一些技巧。 @MarcGlisse:可能是这样,如果您愿意,请随时尝试并改进我的库。如果可以做得更好,我会很高兴。坦率地说,我认为我的解决方案不会引入任何开销,也不会对客户端施加任何限制,这对我的目的来说很好。但这并不意味着它是完美的,当然 FWIW,过去我已经验证 GCC 和 VC++ 都完全优化了发布版本中的引用元组 - 相同 代码生成不使用元组(使用 Boost 进行测试.Fusion)。 【参考方案1】:

由于我对我的发现不满意,我尝试自己制定一个解决方案,并最终编写了一个小型库,它允许对参数包制定通用操作。我的解决方案有以下特点:

允许迭代参数包的所有或一些元素,可能通过计算它们在包上的索引来指定; 允许将参数包的计算部分转发给可变函子; 只需要包含一个相对较短的头文件; 广泛使用完美转发以允许大量内联,并避免不必要的复制/移动以将性能损失降至最低; 迭代算法的内部实现依赖于空基类优化来最小化内存消耗; 很容易扩展和适应(相对而言,考虑到它是模板元编程)。

我将首先展示该库可以做什么然后发布它的实现

用例

这是一个示例,说明如何使用 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&lt;&gt; 元函数允许确定参数包中的所有类型是否是同质的,主要用于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 只是仿函数的 语法糖,因此它们也可以与上述算法结合使用;但是,在 C++ 支持 generic lambdas 之前,这仅适用于 homogeneous 参数包。以下示例还显示了 homogeneous-type&lt;&gt; 元函数的用法,它返回同质包中所有参数的类型:

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

这基本上是库允许做的事情,但我相信它甚至可以扩展以执行更复杂的任务。

实施

现在是实现,它本身有点棘手,所以我将依靠 cmets 来解释代码并避免使这篇文章太长(也许已经是):

#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://***.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)...);

结论

当然,即使我对这个问题提供了自己的答案(实际上因为这个事实),我很想知道是否存在我错过的替代或更好的解决方案 - 除了问题的“相关作品”部分中提到的那些。

【讨论】:

这确实值得更多的支持。我喜欢static ifstatic for 的想法,对我而言,它们会使元编程更易于阅读、编写和理解,但如果这些在标准库中实现,我完全不介意. @chris:实际上,大部分功能都可以通过 Boost.Fusion 实现,方法是首先将参数包转换为元组(在我写这篇文章的时候,我不知道编译器会优化元组离开)。但是感谢您的赞赏:) 嗯,我不知道 Boost Fusion 可以做到这一点。老实说,我对 Boost 的所有经验都很少(我是在配备 C++11 后才发现的),但是 Fusion 库和其他一些库激发了我的兴趣。 我知道这个答案是三年前写的,但我希望你能回答我:为什么你没有用 O(1) 实现写last_type_of?任何人都会编写一个 1024 参数函数(即我不是在谈论模板深度问题),但您可以减少编译时间。谢谢。 @Manu343726:那只是 6 个月前,而不是 3 年 :D 我不关心复杂性,因为它都是在编译时完成的,所以计算复杂性通常不是问题。我只是写了我想到的最简单的实现:)【参考方案2】:

让我根据讨论发布这段代码:

#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只包含对print的3次调用,没有扩展助手的踪迹。

【讨论】:

感谢您花时间尝试回答我的问题。目前我对您的解决方案有一个评论(并不意味着它无法修复):它不使用完美转发。对于intdoubles,这并不重要,但对于UDT,这意味着它将生成副本和移动。而且你不能把EXPAND(print(args))改成EXPAND(print(forward(args))),因为宏预处理器会喊一些不礼貌的东西。 我刚刚添加了forward(谢谢,我忘记添加了),宏预处理器根本没有抱怨...... 你是对的,出于某种原因,我认为预处理器不喜欢尖括号。不知道为什么。所以我想这是我的方法的有效替代方案,用于更简单的情况,您想要迭代一个范围的所有元素。它还有一个优点,即它允许调用的函数成为一个函数模板,而不仅仅是一个仿函数,正如我的解决方案所需要的那样。 我将接受我自己的答案,因为我相信它涵盖了问题的更普遍精神(如何对参数包执行通用操作)。您的技术非常有趣,因为它紧凑且不需要仿函数,但它适用于整个参数列表,并且不允许轻松拆分包或选择应通过其进行迭代的子包。虽然绝对是 +1 好的。请注意,这两种方法可以轻松组合。转发一次以使 index_range 可用,然后使用 EXPAND 执行涉及对象及其索引的任何代码(它不介意同时扩展 2 个包)。【参考方案3】:

使用 enumerate 解决方案(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:函数enumerate 和模块itertools 是很好的起点:)【参考方案4】:

... 符号确实有一些有趣的选项,例如:

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 需要返回一些东西。

如果您不关心订单,这个技巧会很有用。

【讨论】:

请参阅gitorious.org/redistd/redistd/blobs/master/include/redi/… 了解一种强制排序的方法,使用标准函数式编程技巧打印参数包的头部,然后递归处理尾部【参考方案5】:

在阅读了一些其他帖子并修改了一段时间后,我想出了以下内容(与上述内容有些相似,但实现方式略有不同)。这是我使用 Visual Studio 2013 编译器编写的。

使用 lambda 表达式 -

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

使用 lambda 的缺点是参数必须与 lambda 的参数列表中声明的类型相同。这意味着它只适用于一种类型。如果要使用模板化函数,可以使用下一个示例。

使用 struct wrapper functor -

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

希望人们觉得这很有用:-)

【讨论】:

以上是关于如何对可变参数模板函数的异构参数包进行通用计算?的主要内容,如果未能解决你的问题,请参考以下文章

第21课 可变参数模板_展开参数包

如何对可变参数函数中的所有参数调用 std::forward ?

如何使通用可变参数函数中先前声明的函数的返回类型成功进行上下文推断?

如何使用可变参数模板参数保存可变数量的参数?

如何将构造函数(可变参数)作为模板参数传递?

C++11 ——— 可变参数模板