C++ 获取第n个可变参数值(非类型)

C++ 获取第n个可变参数值(非类型),c++,arguments,variadic-templates,c++14,C++,Arguments,Variadic Templates,C++14,忽略缺失的完美转发。(假设参数在实际实现中完全转发。) 但是,如果我希望以3而不是2或任何其他数字的组传递参数,则需要再次实现此解决方案。我想要一些动态的东西,在这里我可以指定通过模板参数传递给每个mFn调用的参数数量。比如: forEachNArgs<3>([](auto a1, auto a2, auto a3){ /*...*/ }, /*...*/); forEachNArgs<4>([](auto a1, auto a2, auto a3, auto a4){

忽略缺失的完美转发。(假设参数在实际实现中完全转发。)

但是,如果我希望以3而不是2或任何其他数字的组传递参数,则需要再次实现此解决方案。我想要一些动态的东西,在这里我可以指定通过模板参数传递给每个
mFn
调用的参数数量。比如:

forEachNArgs<3>([](auto a1, auto a2, auto a3){ /*...*/ }, /*...*/);
forEachNArgs<4>([](auto a1, auto a2, auto a3, auto a4){ /*...*/ }, /*...*/);
forEachNArgs([](自动a1、自动a2、自动a3){/*…*/}、/*…*/);
forEachNArgs([](自动a1、自动a2、自动a3、自动a4){/*…*/}、/*…*/);

忽略请求的完美转发,这应该可以:

template<typename B, typename C>
struct forEachNArgsImpl;

template<std::size_t... Bs, std::size_t... Cs>
struct forEachNArgsImpl<
    std::index_sequence<Bs...>,
    std::index_sequence<Cs...>
>
{
    template<std::size_t N, typename TF, typename... Ts>
    static void execN(TF mFn, const std::tuple<Ts...>& mXs)
    {
        mFn( std::get< N + Cs >( mXs )... );
    }

    template<typename TF, typename... Ts>
    static void exec(TF mFn, const std::tuple<Ts...>& mXs)
    {
        using swallow = bool[];
        (void)swallow{ (execN< Bs * sizeof...(Cs) >( mFn, mXs ), true)... };
    }
};

template<std::size_t N, typename TF, typename... Ts>
void forEachNArgs(TF mFn, Ts... mXs)
{
    static_assert( sizeof...(Ts) % N == 0, "Wrong number of arguments" );
    forEachNArgsImpl<
        std::make_index_sequence<sizeof...(Ts)/N>,
        std::make_index_sequence<N>
    >::exec(mFn, std::forward_as_tuple( mXs... ) );
}
模板
结构forEachNArgsImpl;
模板
结构forEachNArgsImpl<
std::索引_序列,
std::index_序列
>
{
模板
静态void execN(TF-mFn、const-std::tuple和mXs)
{
最惠国待遇(标准:获取(mXs)…);
}
模板
静态void exec(TF-mFn、const-std::tuple和mXs)
{
使用swallow=bool[];
(无效){(execN(最惠国待遇,mXs),正确)…};
}
};
模板
无效外汇(TF最惠国、Ts…mXs)
{
静态断言(sizeof…(Ts)%N==0,“参数数目错误”);
forEachNArgsImpl<
std::生成索引序列,
std::生成索引序列
>:exec(最惠国,标准::转发组件(mXs…);
}
以下内容可能会有所帮助:

namespace detail
{

    template<std::size_t...IsN, std::size_t...Is, typename F>
    void forEachNArgsImpl(std::index_sequence<IsN...>, std::index_sequence<Is...>, F) { }

    template<std::size_t...IsN, std::size_t...Is, typename F, typename... Ts>
    void forEachNArgsImpl(std::index_sequence<IsN...> isn, std::index_sequence<Is...>, F f, Ts... mXs)
    {
        f(std::get<IsN>(std::forward_as_tuple(std::forward<Ts>(mXs)...))...);
        constexpr std::size_t N = sizeof...(IsN);
        constexpr std::size_t is = sizeof...(Is);
        forEachNArgsImpl(isn,
                         std::make_index_sequence<(is > N) ? sizeof...(Is) - N : 0>{},
                         f,
                         std::get<N + Is>(std::forward_as_tuple(std::forward<Ts>(mXs)...))...);
    }

}

template<std::size_t N, typename F, typename... Ts> void forEachNArgs(F f, Ts... args)
{
    static_assert(sizeof...(Ts) % N == 0, "Wrong number of arguments");
    detail::forEachNArgsImpl(std::make_index_sequence<N>{}, std::make_index_sequence<sizeof...(Ts) - N>{}, f, std::forward<Ts>(args)...);
}
名称空间详细信息
{
模板
void forEachNArgsImpl(std::index_序列,std::index_序列,F){}
模板
void forEachNArgsImpl(std::index_序列为isn,std::index_序列为F,Ts…mXs)
{
f(std::get(std::forward_as_tuple(std::forward(mXs)…)…);
constexpr std::size\u t N=sizeof…(IsN);
constexpr std::size\u t is=sizeof…(is);
forEachNArgsImpl(国际标准化组织,
std::make_index_sequence N)?sizeof…(Is)-N:0>{},
F
std::get(std::forward_as_tuple(std::forward(mXs)…)…);
}
}
模板void forEachNArgs(F,Ts…args)
{
静态断言(sizeof…(Ts)%N==0,“参数数目错误”);
detail::forEachNArgsImpl(std::make_index_sequence{},std::make_index_sequence{},f,std::forward(args)…);
}

其核心是
用一些
调用,它接受一个callable和一个包含索引和vararg的包,并用varargs的索引调用callable

一些索引帮助程序:

template<size_t K, class indexes>
struct offset_indexes;
template<size_t K, size_t...Is>
struct offset_indexes<K, std::index_sequence<Is...>>:
  std::index_sequence<(K+Is)...>
{};
现在是问题的关键
call\u by\u n
是存储另一个函数对象的函数对象。它获取一系列偏移量,然后使用这些偏移量对参数的偏移量(乘以
n
)调用
F
,并传入
n
参数:

template<class F, size_t n>
struct call_by_n {
  F&& f;
  // Offset... should be `<0, ..., sizeof...(Args)/n -1>`
  template<size_t...Offset, class...Args>
  void operator()(std::index_sequence<Offset...>, Args&&...args) {
    static_assert(0==(sizeof...(Args)%n), "Number of args must be divisible by n");
    // <0,1,2,3,4,...,n-1> sequence:
    using indexes = std::make_index_sequence<n>;

    using discard=int[];

    // the unused array trick to expand an arbitrary call:
    (void)discard{0,(
      ( call_with_some( f, offset_indexes<Offset*n, indexes>{}, std::forward<Args>(args)...) )
    ,void(),0)...};
  }
  void operator()() {} // do nothing, naturally
};
模板
结构调用{
F&F;
//抵消…应该是``
模板
void运算符(){
静态断言(0==(sizeof…(Args)%n),“Args的数量必须能被n整除”);
//顺序:
使用indexes=std::生成索引序列;
使用discard=int[];
//扩展任意调用的未使用数组技巧:
(作废)丢弃{0(
(使用一些(f,offset_索引{},std::forward)调用_——很好,没有输入错误

此版本现在执行“平面”样式的调用,没有无界递归。递归调用的数量不会随着
Args…
n
的增加而增加

discard
技巧有点混乱。我们创建了一个充满零的临时整数数组,作为副作用,在参数包扩展中执行任意代码。临时整数数组从不读取,也不获取其地址,因此编译器可以将其消除,就好像它从未存在一样


在C++1z中,带有
的折叠表达式将允许我们在几乎没有太多样板或魔术的情况下执行类似的操作。

以下是C++Now2014上演示的一个变体:

#包括
#包括
#包括
结构类型_擦除{};
模板
结构包装器:类型\u擦除{
包装器(T&&w):w_(std::forward(w)){}
T&w;
decltype(auto)get(){return std::forward(w_);}
};
模板
用于(T&&x)的包装器{
返回{std::forward(x)};
}
模板
结构查找;
模板
结构查找{
模板
静态数据类型(自动)
在_位置(decltype(ignore,type _erasure())…,包装器w…){
返回w.get();
}
模板
静态自动
all_after(decltype(ignore,type_erasure())…,Ts&…args){
return std::forward_as_tuple(args.get()…);
}
};
模板
自动获取第n个参数(Args&&…Args){
返回查找::在_位置(
(std::forward(args))的包装器\u。。。
);
}
模板
自动获取ALLAFTER(Args&&…Args){
返回查找::后面的所有\u(
(std::forward(args))的包装器\u。。。
);
}
int main()
{
断言(getNth(1,2,3)==1);
断言(getNth(1,2,3)==2);
断言(getNth(1,2,3)==3);
断言(getAllAfter(2,4,6,8,10)=std::make_tuple(8,10));
}

使用
std::tie
来拥有一个不复制其参数的元组。@DanielFrey:似乎
std::tie
采用l值引用。如果我将一个r值引用传递给
forEach2Args
并打算在
mFn
调用过程中移动它,它不会阻止正确的转发吗?@VittorioRomeo,似乎是正确的t方法是
std::forward\u as_tuple(std::forward(values)…。
@VittorioRomeo您仍然在函数的模板参数中编码l/r值,因此它可以恢复。(或者只使用
forward\u as_tuple
getAllAfter(mXs…)
无法返回多个值。几分钟前添加了一个关于此选项的编辑implementation@VittorioRomeo我想出了一个更通用、非递归的版本,请查看编辑后的答案。非常、非常漂亮、优雅!比。对于空包,它在
swallow
中遗漏了
true
值。平面解决方案--固定递归深度
template<size_t K, class indexes>
struct offset_indexes;
template<size_t K, size_t...Is>
struct offset_indexes<K, std::index_sequence<Is...>>:
  std::index_sequence<(K+Is)...>
{};
// SFINAE test optional, but why not:
template<class F, class...Ts, size_t... Is>
std::result_of_t< F( std::tuple_element_t< Is, std::tuple<Ts&&...> >... ) >
call_with_some( F&& f, std::index_sequence<Is...>, Ts&&... ts ) {
  return std::forward<F>(f)(
    std::get<Is>(
      std::forward_as_tuple(std::forward<Ts>(ts)...)
    )...
  );
}
template<class F, size_t n>
struct call_by_n {
  F&& f;
  // Offset... should be `<0, ..., sizeof...(Args)/n -1>`
  template<size_t...Offset, class...Args>
  void operator()(std::index_sequence<Offset...>, Args&&...args) {
    static_assert(0==(sizeof...(Args)%n), "Number of args must be divisible by n");
    // <0,1,2,3,4,...,n-1> sequence:
    using indexes = std::make_index_sequence<n>;

    using discard=int[];

    // the unused array trick to expand an arbitrary call:
    (void)discard{0,(
      ( call_with_some( f, offset_indexes<Offset*n, indexes>{}, std::forward<Args>(args)...) )
    ,void(),0)...};
  }
  void operator()() {} // do nothing, naturally
};
template<size_t n, class F, class...Args>
void forEachNArgs(F&& f, Args&&...args) {
  static_assert( (sizeof...(Args)%n)==0, "Wrong number of arguments" );
  call_by_n<F,n>{std::forward<F>(f)}(std::make_index_sequence<sizeof...(Args)/n>{}, std::forward<Args>(args)...);
}