如何在可变参数模板函数的异构参数包上进行泛型计算?

And*_*owl 77 c++ iteration template-meta-programming variadic-templates c++11

前提:

在稍微使用了可变参数模板之后,我意识到实现稍微超出简单的元编程任务的任何东西很快变得非常麻烦.特别是,我发现自己希望的执行方式上的参数包一般操作迭代,拆分,循环在一个std::for_each样的方式,等等.

在观看了由安德烈Alexandrescu的本次讲座由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));
    }
}
Run Code Online (Sandbox Code Playgroud)

哪个会在编译时被翻译成这样的东西:

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));
}
Run Code Online (Sandbox Code Playgroud)

原则上,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));
    }
}
Run Code Online (Sandbox Code Playgroud)

或者像这样一个更具表现力的习语:

template<typename... Ts>
void foo(Ts&&... args)
{
    static for_each (auto&& x : args)
    {
        foo(x);
    }
}
Run Code Online (Sandbox Code Playgroud)

相关工作:

我在网上做了一些搜索,发现确实存在某些东西:

  • 此链接描述了如何将参数包转换为Boost.MPL向量,但这只是朝向目标的一半(如果不是更少);
  • 在SO这个问题似乎需要一个类似并稍微相关的元编程功能(分裂的参数包了两半) -实际上,上有SO几个问题,这似乎与此有关的问题,但没有回答我已阅读令人满意地解决恕我直言;
  • Boost.Fusion定义了将参数包转换为元组的算法,但我更喜欢:
    1. 不要创建不必要的临时工具来保存可以(并且应该)完美转发到某些通用算法的参数;
    2. 有一个小的,自包含的库来做到这一点,而Boost.Fusion可能包括比解决这个问题所需的更多的东西.

题:

是否有一种相对简单的方法,可能通过一些模板元编程,来实现我正在寻找的东西,而不会产生现有方法的局限性?

And*_*owl 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)...);
}
Run Code Online (Sandbox Code Playgroud)

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
        );
}
Run Code Online (Sandbox Code Playgroud)

有时,人们可能只想将参数包的一部分转发给其他可变参数函数而不是遍历其元素并将它们中的每一个单独传递给非变量仿函数.这是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)...);
}
Run Code Online (Sandbox Code Playgroud)

对于更具体的任务,当然可以通过索引来检索包中的特定参数.这是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;
}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果参数包是同质的(即所有参数具有相同的类型),则可能优选诸如下面的参数.所述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;
}
Run Code Online (Sandbox Code Playgroud)

最后,由于lambda只是仿函数的语法糖,因此它们也可以与上述算法结合使用; 但是,在C++支持通用lambdas之前,这只适用于同类参数包.以下示例还示出了的使用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)...);
Run Code Online (Sandbox Code Playgroud)

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

实施

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

#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)...);
}
Run Code Online (Sandbox Code Playgroud)

结论

当然,即使我提供我自己回答这个问题(实际上是因为这个事实),我很好奇听到的话,替代或更好的解决办法是存在,我已经错过了-除了"相关工作"一节中提到的那些这个问题.

  • 诚实地,这应该得到比它更多的赞成.我喜欢`static if`和`static for`这个概念,对我来说,他们会让元编程更容易阅读,编写和理解,但是如果这些是在标准库. (4认同)
  • @chris:实际上,大部分功能都可以通过Boost.Fusion实现,首先将参数包转换为元组(当我写这篇文章时,我不知道编译器会优化元组).但是谢谢你的赞赏:) (2认同)

Mar*_*sse 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);
}
Run Code Online (Sandbox Code Playgroud)

我检查了生成的代码g++ -std=c++11 -O1并且main只包含3个调用print,没有跟踪扩展帮助程序.


Mat*_* M. 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
Run Code Online (Sandbox Code Playgroud)

码:

// 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)...);
}
Run Code Online (Sandbox Code Playgroud)

范围构建器(从您的解决方案中获取):

// 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;
Run Code Online (Sandbox Code Playgroud)