【问题标题】:Retrieve value out of "cascading ifs" fold expression从“级联 ifs”折叠表达式中检索值
【发布时间】:2017-09-27 14:07:34
【问题描述】:

假设我想使用以下语法创建自己的基于 lambda 的开关:

auto s = make_switch(std::pair{0, []{ return 0;   }},
                     std::pair{1, []{ return 50;  }},
                     std::pair{2, []{ return 100; }});

assert( s(0) == 0   );
assert( s(1) == 50  );
assert( s(2) == 100 );

我想使用 折叠表达式 以获得不需要递归的简洁实现。这个想法是生成类似于一堆嵌套if 语句的东西:

if(x == 0) return 0;
if(x == 1) return 50;
if(x == 2) return 100;

我想写这个:

// pseudocode
template <typename... Pairs>
auto make_switch(Pairs... ps)
{
    return [=](int x)
    {
        ( if(ps.first == x) return ps.second(), ... );
    };
}

上面的代码不起作用,因为if(...){...} 不是表达式。然后我尝试使用&amp;&amp; 运算符:

template <typename... Pairs>
auto make_switch(Pairs... ps)
{
    return [=](int x)
    {
        return ((ps.first == x && ps.second()), ...);
    };
}

这确实编译,但返回ps.first == x &amp;&amp; ps.second() 的结果,这是一个bool,而不是我想要的int 值。

我想要某种运算符,它是 逗号运算符 和 &amp;&amp; 之间的组合:它应该计算并计算到运算符的 右侧如果 左侧 计算结果为 true。

我想不出任何技术可以让我以这样的方式实现它,我可以获取ps.second() 的返回值并将其传播给make_switch 返回的 lambda 的调用者。

是否可以使用 折叠表达式 实现这种“级联 ifs”模式?我只想评估所需数量的表达式,直到找到一个匹配的分支。

【问题讨论】:

  • 由于您所有的typename Pairs::second_type... 都不同,我认为您只能通过递归来做到这一点。
  • @j6t: 但所有decltype(ps.second)() 都是一样的。我的意图是生成一组带有 fold 表达式 的if 语句
  • 他们不一样。您的示例调用中的每个 lambda 都是不同的类型。它们不可能相同,因为每个 lambda 的 operator() 实现都是不同的,即使它们看起来具有相同的签名。
  • @j6t:对不起,我搞砸了我的评论。我的意思是他们都返回int。 (即decltype(ps.second()) 是一样的)

标签: c++ c++17 fold fold-expression


【解决方案1】:

我很惊讶它还没有被建议:

template <typename ...Pairs> auto make_switch(Pairs ...ps)
{
    return [=](int x)
    {
        int ret;
        ((x == ps.first && (void(ret = ps.second()), 1)) || ...)
            /* || (throw whatever, 1) */ ;
        return ret;
    };
}

(try it online)

它需要一个额外的变量,但似乎唯一的选择是递归和一个带有重载二元运算符的包装类,在我看来两者都不那么优雅。

|| 的短路用于在找到匹配时停止函数。

(对于上面的代码,GCC 7.2 给了我warning: suggest parentheses around '&amp;&amp;' within '||'。可能是一个错误?)

编辑:

这是一个适用于任何类型的通用版本:(感谢@Barry 建议std::optional)

template <typename InputType, typename ReturnType, typename ...Pairs> auto make_switch(Pairs ...ps)
{
    /* You could do
     *   using InputType  = std::common_type_t<typename Pairs::first_type...>;
     *   using ReturnType = std::common_type_t<decltype(ps.second())...>;
     * instead of using template parameters.
     */
    
    return [=](InputType x)
    {
        std::optional<ReturnType> ret /* (default_value) */;
        ( ( x == ps.first && (void(ret.emplace(std::move(ps.second()))), 1) ) || ...)
            /* || (throw whatever, 1) */;
        return *ret;
    };
}

(try it online)

我决定对参数和返回类型使用模板参数,但你可以根据需要推断它们。

请注意,如果您决定既没有默认值也没有throw,那么将无效值传递给开关会给您UB。

【讨论】:

  • 有趣。有什么方法可以将其推广到 T 而不是 int,支持仅提供构造的类型?
  • @VittorioRomeo 我有一些想法,一会儿会做代码。
  • @HolyBlackCat - ( (ret = ps.second(), ps.first == x) || ... ); 怎么样?
  • @max66 它更短,但效率较低(除非编译器对其进行了优化,我不想依赖它)。
  • @HolyBlackCat - 是的......这是个坏主意;强加了很多应该避免的任务。
【解决方案2】:

这是不可能的。为了使用折叠表达式,您需要在 Pairs 上定义一个二元运算符。

在你的情况下,这样的二元运算符不能存在,因为:

  • 它需要是有状态的(即捕获 x,因为它将 Pairs::first 与 x 进行比较)
  • and 运算符必须是 (i) 非静态成员函数或 (ii) 非成员函数。

此外:

  • (i) 非静态成员运算符隐式将this 作为第一个参数,您不能将this 设为指向Pairs 的指针或Pairs 的派生对象;
  • (ii) 非成员函数无法捕获 x 的值。

【讨论】:

    【解决方案3】:

    我认为 HolyBlackCat 的解决方案更好,但是......使用 sum 怎么样?

    template <typename ... Pairs>
    auto make_switch (Pairs ... ps)
     {
       return [=](int x)
        { return ( (ps.first == x ? ps.second() : 0) + ... ); };
     }
    

    很遗憾,它仅适用于定义了总和的类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-26
      • 2018-02-13
      • 1970-01-01
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多