9

假设我想使用以下语法创建自己的基于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(...){...}它不是表达式。然后我尝试使用&&运算符:

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

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

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

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

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

4

3 回答 3

17

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

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

(在线尝试)

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

的短路||用于在找到匹配时停止该功能。

(对于上面的代码 GCC 7.2 给了我warning: suggest parentheses around '&&' 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;
    };
}

(在线尝试)

我决定对参数和返回类型使用模板参数,但如果你愿意,你可以推导出它们。

请注意,如果您决定没有默认值 nor throw,则将无效值传递给开关将为您提供 UB。

于 2017-09-27T15:29:08.747 回答
0

这是不可能的。为了使用折叠表达式,您需要在Pairs.

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

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

此外:

  • (i) 非静态成员运算符隐含地this作为第一个参数,并且您不能创建this指向 aPairs或派生的指针Pairs
  • (ii) 非成员函数无法捕获 的值x
于 2017-09-27T14:33:37.393 回答
0

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

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

不幸的是,仅适用于定义了总和的类型。

于 2017-09-27T17:04:46.170 回答