【问题标题】:Iteratively filtering arguments matching a predicate at compile-time在编译时迭代过滤与谓词匹配的参数
【发布时间】:2018-02-26 06:04:22
【问题描述】:

上下文

首先,一些上下文:我使用一个名为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 的辅助函数,它通过解包 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);

此函数将通过完美转发编译时is_nothing_v&lt;T&gt;返回false的所有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));
}

递归案例采用fxxs...,如果!is_nothing_v&lt;decltype(f)&gt; 通过 lambda 有条件地绑定x 作为f 的参数之一。然后它在 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 扩展 来过滤掉参数而不递归。

是否可以在不递归的情况下实现call_ignoring_nothing我想不出任何允许在包扩展期间过滤掉参数的技术。

【问题讨论】:

  • 重申我在 Slack 上提出的建议:将包变成一个元组。为元组生成索引序列。过滤索引序列(甚至可能使用类型级别的过滤器,将序列转换为integral_constant&lt;std::size_t, Is&gt;...)。模式匹配。展开get&lt;FilteredIs&gt;(tuple)...
  • 还从 Slack 移植了我的评论,作为对“这不是将问题转移到 filter 内的 concat 级别”的答案 - 您可以优化 join 以避免总是递归;可以在metal 中找到一个示例。
  • 正如 Desproges 所说:“就我自己的观点而言,我经常提到的观点......”我认为任何人都可以在这里发布答案。

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


【解决方案1】:

与 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    
 }

live example on wandbox

【讨论】:

  • 哦,这很整洁。我遇到的一个问题是它取决于tuple_cat 在您的实现中没有 QoI 错误(即里程可能因 stdlib 实现而异)。
  • @Griwes - 嗯……抱歉,我不知道什么是“Qol bug”;你能给我推荐一个参考吗?
  • “QoI”是“实施质量”。据推测,“QoI 错误”是一种次优的实施策略。 (例如,如果库的 tuple_cat 被实现为普通的旧递归,那么您没有购买任何性能方面的东西;您只是将性能瓶颈推到库代码中。)
  • @Quuxplusone - 我明白了......有趣而且......是的:也许我只是转移了问题;没有解决它;谢谢。一个疑问:使用可变参数模板,并不意味着(在编译器实现中)同样的递归问题?
  • s/make_tuple/forward_as_tuple/
【解决方案2】:

这是另一个不依赖于tuple_cat 的镜头。首先通过“正常” 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)...);
}

【讨论】:

  • 整洁!我只是意识到if constexpr 我通常在constexpr functionsconstexpr function-templates 中使用是多余的......甚至其他答案也会成为这个的受害者:-)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-15
  • 1970-01-01
  • 2012-03-22
相关资源
最近更新 更多