在编译时迭代过滤与谓词匹配的参数
Iteratively filtering arguments matching a predicate at compile-time
上下文
首先,一些上下文:我正在使用一个名为 nothing
的空 struct
来模拟类似于 "regular void
" 的东西,以美化一些依赖于链接多个函数对象的接口在一起。
struct nothing { };
用法示例:
when_all([]{ return 0; }, []{ }, []{ return 'a'; })
.then([](int, char){ }); // result of lambda in the middle ignored
在上面的示例中,实际发生的是我将传递给 when_all
的函数对象的所有结果打包在 std::tuple
中,将 void
转换为 nothing
(在这个例子中:std::tuple<int, nothing, char>
),然后我使用一个名为 apply_ignoring_nothing
的辅助函数,它通过解包一个 [=21] 来调用一个函数对象=], 忽略 nothing
.
的元素
auto f_then = [](int, char){ };
auto args = std::tuple{0, nothing{}, 'a'};
apply_ignoring_nothing(f_then, args); // compiles
apply_ignoring_nothing
是根据 call_ignoring_nothing
.
实现的
问题
我有一个具有以下签名的函数 call_ignoring_nothing
:
template <typename F, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs);
此函数将通过完美转发编译时 is_nothing_v<T>
returns false
.[=49= 的所有 xs...
来调用 f
]
is_nothing_v
定义如下:
template <typename T>
inline constexpr bool is_nothing_v = std::is_same_v<std::decay_t<T>, nothing>;
我实现call_ignoring_nothing
的方式是递归。基本情况只需要 f
并简单地调用它:
#define FWD(x) ::std::forward<decltype(x)>(x)
template <typename F>
constexpr decltype(auto) call_ignoring_nothing(F&& f)
{
return returning_nothing_instead_of_void(FWD(f));
}
递归情况采用 f
、x
和 xs...
,并且有条件地将 x
作为 f
的参数之一绑定,如果 !is_nothing_v<decltype(f)>
通过 lambda。然后它递归 call_ignoring_nothing
传递新创建的 lambda 作为 f
:
template <typename F, typename T, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, T&& x, Ts&&... xs)
{
return call_ignoring_nothing(
[&](auto&&... ys) -> decltype(auto) {
if constexpr(is_nothing_v<T>)
{
return FWD(f)(FWD(ys)...);
}
else
{
return FWD(f)(FWD(x), FWD(ys)...);
}
},
FWD(xs)...);
}
我想以迭代的方式实现 call_ignoring_nothing
,可能利用 pack expansion 来过滤掉参数而不用递归。
是否可以不用递归实现call_ignoring_nothing
?我想不出任何允许在包扩展期间过滤掉参数的技术。
与 Griwes 的建议没有太大不同,但是......我想你可以使用 std::apply()
、std::tuple_cat()
、std::get()
和空的元组或具有值的元组is_nothing_v
.
我的意思是...类似 [编辑:根据 T.C 的建议进行了改进。以及来自 OP 本身的示例 (Vittorio Romeo)]
template <bool B, typename ... Ts>
constexpr auto pick_if (Ts && ... xs)
{
if constexpr ( B )
return std::forward_as_tuple(std::forward<Ts>(xs)...);
else
return std::tuple{};
}
template <typename F, typename ... Ts>
constexpr decltype(auto) call_ignoring_nothing (F && f, Ts && ... xs)
{
return std::apply(f,
std::tuple_cat(pick_if<!is_nothing_v<Ts>>(std::forward<Ts>(xs))...)
);
}
下面是一个工作示例
#include <tuple>
#include <iostream>
#include <type_traits>
struct nothing { };
template <typename T>
constexpr bool is_nothing_v = std::is_same<std::decay_t<T>, nothing>::value;
template <bool B, typename ... Ts>
constexpr auto pick_if (Ts && ... xs)
{
if constexpr ( B )
return std::forward_as_tuple(std::forward<Ts>(xs)...);
else
return std::tuple{};
}
template <typename F, typename ... Ts>
constexpr decltype(auto) call_ignoring_nothing (F && f, Ts && ... xs)
{
return std::apply(f,
std::tuple_cat(pick_if<!is_nothing_v<Ts>>(std::forward<Ts>(xs))...)
);
}
float foo (int a, float b) { return a + b; }
int main ()
{
std::cout << call_ignoring_nothing(foo, nothing{}, 12, nothing{},
2.3f, nothing{}); // print 14.3
}
这是另一个不依赖于 tuple_cat
的镜头。首先通过 "normal" constexpr 函数模板计算一组布尔值 true
的位置:
template<class... Bools>
constexpr int count(Bools... bs)
{
return (bool(bs) + ...);
}
template<bool... bs>
constexpr std::array<std::size_t, count(bs...)> indices()
{
std::array<std::size_t, count(bs...)> ret = {};
std::size_t i = 0, j = 0;
for(bool b : {bs...}) {
if(b) {
ret[j] = i;
++j;
}
++i;
}
return ret;
}
然后将结果转换为index_sequence
:
template<bool...bs, std::size_t...Is>
constexpr auto indices_as_sequence_helper(std::index_sequence<Is...>)
{
return std::index_sequence<indices<bs...>()[Is]...>{};
}
template<bool...bs>
constexpr auto indices_as_sequence()
{
return indices_as_sequence_helper<bs...>(std::make_index_sequence<count(bs...)>());
}
那么 forward_as_tuple
+ get
和 index_sequence
:
就很简单了
template <typename F, typename... Ts, std::size_t... Is>
constexpr decltype(auto) call_some(std::index_sequence<Is...>, F&& f, Ts&&... xs)
{
return std::forward<F>(f)(
std::get<Is>(std::forward_as_tuple(std::forward<Ts>(xs)...))...);
}
template <typename F, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs)
{
return call_some(indices_as_sequence<!is_nothing_v<Ts>...>(),
std::forward<F>(f), std::forward<Ts>(xs)...);
}
上下文
首先,一些上下文:我正在使用一个名为 nothing
的空 struct
来模拟类似于 "regular void
" 的东西,以美化一些依赖于链接多个函数对象的接口在一起。
struct nothing { };
用法示例:
when_all([]{ return 0; }, []{ }, []{ return 'a'; })
.then([](int, char){ }); // result of lambda in the middle ignored
在上面的示例中,实际发生的是我将传递给 when_all
的函数对象的所有结果打包在 std::tuple
中,将 void
转换为 nothing
(在这个例子中:std::tuple<int, nothing, char>
),然后我使用一个名为 apply_ignoring_nothing
的辅助函数,它通过解包一个 [=21] 来调用一个函数对象=], 忽略 nothing
.
auto f_then = [](int, char){ };
auto args = std::tuple{0, nothing{}, 'a'};
apply_ignoring_nothing(f_then, args); // compiles
apply_ignoring_nothing
是根据 call_ignoring_nothing
.
问题
我有一个具有以下签名的函数 call_ignoring_nothing
:
template <typename F, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs);
此函数将通过完美转发编译时 is_nothing_v<T>
returns false
.[=49= 的所有 xs...
来调用 f
]
is_nothing_v
定义如下:
template <typename T>
inline constexpr bool is_nothing_v = std::is_same_v<std::decay_t<T>, nothing>;
我实现call_ignoring_nothing
的方式是递归。基本情况只需要 f
并简单地调用它:
#define FWD(x) ::std::forward<decltype(x)>(x)
template <typename F>
constexpr decltype(auto) call_ignoring_nothing(F&& f)
{
return returning_nothing_instead_of_void(FWD(f));
}
递归情况采用 f
、x
和 xs...
,并且有条件地将 x
作为 f
的参数之一绑定,如果 !is_nothing_v<decltype(f)>
通过 lambda。然后它递归 call_ignoring_nothing
传递新创建的 lambda 作为 f
:
template <typename F, typename T, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, T&& x, Ts&&... xs)
{
return call_ignoring_nothing(
[&](auto&&... ys) -> decltype(auto) {
if constexpr(is_nothing_v<T>)
{
return FWD(f)(FWD(ys)...);
}
else
{
return FWD(f)(FWD(x), FWD(ys)...);
}
},
FWD(xs)...);
}
我想以迭代的方式实现 call_ignoring_nothing
,可能利用 pack expansion 来过滤掉参数而不用递归。
是否可以不用递归实现call_ignoring_nothing
?我想不出任何允许在包扩展期间过滤掉参数的技术。
与 Griwes 的建议没有太大不同,但是......我想你可以使用 std::apply()
、std::tuple_cat()
、std::get()
和空的元组或具有值的元组is_nothing_v
.
我的意思是...类似 [编辑:根据 T.C 的建议进行了改进。以及来自 OP 本身的示例 (Vittorio Romeo)]
template <bool B, typename ... Ts>
constexpr auto pick_if (Ts && ... xs)
{
if constexpr ( B )
return std::forward_as_tuple(std::forward<Ts>(xs)...);
else
return std::tuple{};
}
template <typename F, typename ... Ts>
constexpr decltype(auto) call_ignoring_nothing (F && f, Ts && ... xs)
{
return std::apply(f,
std::tuple_cat(pick_if<!is_nothing_v<Ts>>(std::forward<Ts>(xs))...)
);
}
下面是一个工作示例
#include <tuple>
#include <iostream>
#include <type_traits>
struct nothing { };
template <typename T>
constexpr bool is_nothing_v = std::is_same<std::decay_t<T>, nothing>::value;
template <bool B, typename ... Ts>
constexpr auto pick_if (Ts && ... xs)
{
if constexpr ( B )
return std::forward_as_tuple(std::forward<Ts>(xs)...);
else
return std::tuple{};
}
template <typename F, typename ... Ts>
constexpr decltype(auto) call_ignoring_nothing (F && f, Ts && ... xs)
{
return std::apply(f,
std::tuple_cat(pick_if<!is_nothing_v<Ts>>(std::forward<Ts>(xs))...)
);
}
float foo (int a, float b) { return a + b; }
int main ()
{
std::cout << call_ignoring_nothing(foo, nothing{}, 12, nothing{},
2.3f, nothing{}); // print 14.3
}
这是另一个不依赖于 tuple_cat
的镜头。首先通过 "normal" constexpr 函数模板计算一组布尔值 true
的位置:
template<class... Bools>
constexpr int count(Bools... bs)
{
return (bool(bs) + ...);
}
template<bool... bs>
constexpr std::array<std::size_t, count(bs...)> indices()
{
std::array<std::size_t, count(bs...)> ret = {};
std::size_t i = 0, j = 0;
for(bool b : {bs...}) {
if(b) {
ret[j] = i;
++j;
}
++i;
}
return ret;
}
然后将结果转换为index_sequence
:
template<bool...bs, std::size_t...Is>
constexpr auto indices_as_sequence_helper(std::index_sequence<Is...>)
{
return std::index_sequence<indices<bs...>()[Is]...>{};
}
template<bool...bs>
constexpr auto indices_as_sequence()
{
return indices_as_sequence_helper<bs...>(std::make_index_sequence<count(bs...)>());
}
那么 forward_as_tuple
+ get
和 index_sequence
:
template <typename F, typename... Ts, std::size_t... Is>
constexpr decltype(auto) call_some(std::index_sequence<Is...>, F&& f, Ts&&... xs)
{
return std::forward<F>(f)(
std::get<Is>(std::forward_as_tuple(std::forward<Ts>(xs)...))...);
}
template <typename F, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs)
{
return call_some(indices_as_sequence<!is_nothing_v<Ts>...>(),
std::forward<F>(f), std::forward<Ts>(xs)...);
}