获取第 n 个可变参数值(不是类型)
Getting nth variadic argument value (not type)
忽略缺失的完美转发。 (假设参数在实际实现中被完美转发。)
// Base case: no args
template<typename TF> void forEach2Args(TF) { }
// Recursive case: some args
template<typename TF, typename... Ts> void forEach2Args(TF mFn, Ts... mXs)
{
mFn(getNth<0>(mXs...), getNth<1>(mXs...));
forEach2Args(mFn, getAllAfter<2>(mXs...));
}
int main()
{
int result{0};
forEach2Args([&result](auto a1, auto a2)
{
result += (a1 * a2);
}, 2, 4, 3, 6);
// roughly evaluates to:
// result += (2 * 4);
// result += (3 * 6);
}
是否可以实现 getNth
和 getAllAfter
以避免任何可能的运行时开销?到目前为止,我发现的唯一解决方案是在第一个 forEach2Args
调用中将每个 Ts...
放入 std::tuple
中,然后将对该元组的非常量引用传递给每个递归调用。不过,我几乎可以肯定会有不必要的 move/ctor/dtor 调用。
另一个解决方案是使用类似的东西:
// Base case: no args
template<typename TF> void forEach2Args(TF) { }
// Recursive case: some args
template<typename TF, typename T1, typename T2, typename... Ts>
void forEach2Args(TF mFn, T1 mX1, T2 mX2, Ts... mXs)
{
mFn(mX1, mX2);
forEach2Args(mFn, mXs...);
}
但是如果我想以 3 个而不是 2 个或任何其他数字为一组传递参数,则需要再次实施此解决方案。我想要一些动态的东西,我可以通过模板参数指定要传递给每个 mFn
调用的参数数量。类似于:
forEachNArgs<3>([](auto a1, auto a2, auto a3){ /*...*/ }, /*...*/);
forEachNArgs<4>([](auto a1, auto a2, auto a3, auto 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... ) );
}
以下内容可能会有所帮助:
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)...);
}
它的核心是call_with_some
,它接受一个可调用对象和一组索引和可变参数,并使用可变参数的索引调用可调用对象。
一些索引助手:
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_with_some
,已启用 SFINAE。
// 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)...)
)...
);
}
现在是问题的关键。 call_by_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
};
现在我们将上面的内容包装在您的界面中:
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)...);
}
我留下 forEach2Args
作为练习。
live example -- 很好,没有错别字。
此版本现在执行 "flat" 样式调用,没有无限递归。递归调用的数量不会随着 Args...
或 n
.
的增加而增加
discard
技巧有点乱。我们创建一个全为零的临时整数数组,作为副作用在参数包扩展中执行任意代码。临时整数数组永远不会被读取,也不会获取其地址,因此编译器可以将其删除,就好像它从未存在过一样。
在 C++1z 中,使用 ,
的折叠表达式将允许我们做类似的事情,而无需那么多的样板文件或魔法。
以下是 C++Now2014 上的变体:
#include <utility>
#include <tuple>
#include <cassert>
struct type_erasure { };
template<class T>
struct wrapper : type_erasure {
wrapper(T&& w) : w_(std::forward<T>(w)) { }
T&& w_;
decltype(auto) get() { return std::forward<T>(w_); }
};
template<class T>
wrapper<T> wrapper_for(T&& x) {
return { std::forward<T>(x) };
}
template <typename ignore>
struct lookup;
template <std::size_t... ignore>
struct lookup<std::index_sequence<ignore...>> {
template <typename nth>
static decltype(auto)
at_position(decltype(ignore, type_erasure())..., wrapper<nth> w, ...) {
return w.get();
}
template<typename... Ts>
static auto
all_after(decltype(ignore, type_erasure())..., Ts&&... args) {
return std::forward_as_tuple(args.get()...);
}
};
template<std::size_t index, typename... Args>
auto getNth(Args&&... args) {
return lookup<std::make_index_sequence<index>>::at_position(
wrapper_for(std::forward<Args>(args))...
);
}
template<std::size_t index, typename... Args>
auto getAllAfter(Args&&... args) {
return lookup<std::make_index_sequence<index + 1>>::all_after(
wrapper_for(std::forward<Args>(args))...
);
}
int main()
{
assert(getNth<0>(1, 2, 3) == 1);
assert(getNth<1>(1, 2, 3) == 2);
assert(getNth<2>(1, 2, 3) == 3);
assert(getAllAfter<2>(2, 4, 6, 8, 10) == std::make_tuple(8, 10));
}
忽略缺失的完美转发。 (假设参数在实际实现中被完美转发。)
// Base case: no args
template<typename TF> void forEach2Args(TF) { }
// Recursive case: some args
template<typename TF, typename... Ts> void forEach2Args(TF mFn, Ts... mXs)
{
mFn(getNth<0>(mXs...), getNth<1>(mXs...));
forEach2Args(mFn, getAllAfter<2>(mXs...));
}
int main()
{
int result{0};
forEach2Args([&result](auto a1, auto a2)
{
result += (a1 * a2);
}, 2, 4, 3, 6);
// roughly evaluates to:
// result += (2 * 4);
// result += (3 * 6);
}
是否可以实现 getNth
和 getAllAfter
以避免任何可能的运行时开销?到目前为止,我发现的唯一解决方案是在第一个 forEach2Args
调用中将每个 Ts...
放入 std::tuple
中,然后将对该元组的非常量引用传递给每个递归调用。不过,我几乎可以肯定会有不必要的 move/ctor/dtor 调用。
另一个解决方案是使用类似的东西:
// Base case: no args
template<typename TF> void forEach2Args(TF) { }
// Recursive case: some args
template<typename TF, typename T1, typename T2, typename... Ts>
void forEach2Args(TF mFn, T1 mX1, T2 mX2, Ts... mXs)
{
mFn(mX1, mX2);
forEach2Args(mFn, mXs...);
}
但是如果我想以 3 个而不是 2 个或任何其他数字为一组传递参数,则需要再次实施此解决方案。我想要一些动态的东西,我可以通过模板参数指定要传递给每个 mFn
调用的参数数量。类似于:
forEachNArgs<3>([](auto a1, auto a2, auto a3){ /*...*/ }, /*...*/);
forEachNArgs<4>([](auto a1, auto a2, auto a3, auto 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... ) );
}
以下内容可能会有所帮助:
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)...);
}
它的核心是call_with_some
,它接受一个可调用对象和一组索引和可变参数,并使用可变参数的索引调用可调用对象。
一些索引助手:
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_with_some
,已启用 SFINAE。
// 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)...)
)...
);
}
现在是问题的关键。 call_by_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
};
现在我们将上面的内容包装在您的界面中:
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)...);
}
我留下 forEach2Args
作为练习。
live example -- 很好,没有错别字。
此版本现在执行 "flat" 样式调用,没有无限递归。递归调用的数量不会随着 Args...
或 n
.
discard
技巧有点乱。我们创建一个全为零的临时整数数组,作为副作用在参数包扩展中执行任意代码。临时整数数组永远不会被读取,也不会获取其地址,因此编译器可以将其删除,就好像它从未存在过一样。
在 C++1z 中,使用 ,
的折叠表达式将允许我们做类似的事情,而无需那么多的样板文件或魔法。
以下是 C++Now2014 上的变体:
#include <utility>
#include <tuple>
#include <cassert>
struct type_erasure { };
template<class T>
struct wrapper : type_erasure {
wrapper(T&& w) : w_(std::forward<T>(w)) { }
T&& w_;
decltype(auto) get() { return std::forward<T>(w_); }
};
template<class T>
wrapper<T> wrapper_for(T&& x) {
return { std::forward<T>(x) };
}
template <typename ignore>
struct lookup;
template <std::size_t... ignore>
struct lookup<std::index_sequence<ignore...>> {
template <typename nth>
static decltype(auto)
at_position(decltype(ignore, type_erasure())..., wrapper<nth> w, ...) {
return w.get();
}
template<typename... Ts>
static auto
all_after(decltype(ignore, type_erasure())..., Ts&&... args) {
return std::forward_as_tuple(args.get()...);
}
};
template<std::size_t index, typename... Args>
auto getNth(Args&&... args) {
return lookup<std::make_index_sequence<index>>::at_position(
wrapper_for(std::forward<Args>(args))...
);
}
template<std::size_t index, typename... Args>
auto getAllAfter(Args&&... args) {
return lookup<std::make_index_sequence<index + 1>>::all_after(
wrapper_for(std::forward<Args>(args))...
);
}
int main()
{
assert(getNth<0>(1, 2, 3) == 1);
assert(getNth<1>(1, 2, 3) == 2);
assert(getNth<2>(1, 2, 3) == 3);
assert(getAllAfter<2>(2, 4, 6, 8, 10) == std::make_tuple(8, 10));
}