在编译时迭代过滤与谓词匹配的参数

时间:2017-09-17 17:18:02

标签: c++ templates metaprogramming template-meta-programming c++17

上下文

首先,一些上下文:我使用一个名为struct的空nothing来模拟类似于"regular void"的内容,以便美化一些依赖链接多个函数对象的接口在一起。

struct nothing { };

使用示例:

when_all([]{ return 0; }, []{ }, []{ return 'a'; })
    .then([](int, char){ }); // result of lambda in the middle ignored

在上面的示例中,实际发生的是我将when_all中传递给std::tuple的函数对象的所有结果打包,转换{{1} } void (在此示例中为nothing ),然后我使用名为std::tuple<int, nothing, char>的辅助函数通过解压缩来调用函数对象apply_ignoring_nothing,忽略std::tuple的元素。

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); 返回f的所有xs...来调用is_nothing_v<T>

false定义如下:

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)); } fx,并有条件地将xs...绑定为x个参数之一,如果{ {1}}通过lambda。然后它通过f递归将新创建的lambda传递为!is_nothing_v<decltype(f)>

call_ignoring_nothing

我想以迭代的方式实现f,可能会使用 pack expansion 过滤掉参数而不会递归。

是否可以在没有递归的情况下实现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)...); } 我无法想到任何允许在包扩展期间过滤出参数的技术。

2 个答案:

答案 0 :(得分:6)

与Griwes建议没有太大区别,但是......我想你可以使用std::apply()std::tuple_cat()std::get()和空元组,或者根据{{{m}的值使用值1}}。

我的意思是......类似于[编辑:改进了T.C.的建议和OP本身的一个例子(Vittorio Romeo)]

is_nothing_v

以下是一个工作示例

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

live example on wandbox

答案 1 :(得分:1)

这是另一种不依赖tuple_cat的观点。首先通过“普通”constexpr函数模板计算一包bool的位置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 + getindex_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)...);
}