从 "cascading ifs" 折叠表达式中检索值
Retrieve value out of "cascading ifs" fold expression
假设我想使用以下语法创建自己的基于 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()), ...);
};
}
这确实可以编译,但是 return 是 ps.first == x && ps.second()
的结果,它是 bool
而不是我想要的 int
值。
我想要某种运算符,它是 逗号运算符 和 &&
的组合:它应该求值并求值到 右边运算符的手边 当且仅当 左手边 的计算结果为 true
。
我想不出有什么技术可以让我以这样的方式实现它,我可以获得 ps.second()
的 return 值并将其传播给 lambda 的调用者 return编辑者 make_switch
.
是否可以用折叠表达式实现这种"cascading if
s"模式?我只想评估直到找到匹配的分支为止需要多少表达式。
这是不可能的。为了使用折叠表达式,您需要在 Pairs
.
上定义一个二元运算符
在您的情况下,这样的二元运算符不存在,因为:
- 它需要是全状态的(即捕获 x 因为它将
Pairs::first
与 x
进行比较)
- and 运算符必须是 (i) 非静态成员函数或 (ii) 非成员函数。
此外:
- (i) 非静态成员运算符隐式地将
this
作为第一个参数,并且您不能使 this
指向 Pairs
或 Pairs
;
- (ii) 非成员函数无法捕获
x
. 的值
我很惊讶还没有建议:
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;
};
}
它需要一个额外的变量,但似乎唯一的选择是递归和一个带有重载二元运算符的包装器 class,在我看来两者都不那么优雅。
||
的短路用于在找到匹配项时停止该功能。
(对于上面的代码,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;
};
}
我决定对参数和 return 类型使用模板参数,但如果需要,您可以推导它们。
请注意,如果您决定既没有默认值也没有 throw
,那么将无效值传递给开关将会给您 UB。
我认为 HolyBlackCat 的解决方案更好,但是...使用求和怎么样?
template <typename ... Pairs>
auto make_switch (Pairs ... ps)
{
return [=](int x)
{ return ( (ps.first == x ? ps.second() : 0) + ... ); };
}
遗憾的是,仅适用于定义总和的类型。
假设我想使用以下语法创建自己的基于 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()), ...);
};
}
这确实可以编译,但是 return 是 ps.first == x && ps.second()
的结果,它是 bool
而不是我想要的 int
值。
我想要某种运算符,它是 逗号运算符 和 &&
的组合:它应该求值并求值到 右边运算符的手边 当且仅当 左手边 的计算结果为 true
。
我想不出有什么技术可以让我以这样的方式实现它,我可以获得 ps.second()
的 return 值并将其传播给 lambda 的调用者 return编辑者 make_switch
.
是否可以用折叠表达式实现这种"cascading if
s"模式?我只想评估直到找到匹配的分支为止需要多少表达式。
这是不可能的。为了使用折叠表达式,您需要在 Pairs
.
在您的情况下,这样的二元运算符不存在,因为:
- 它需要是全状态的(即捕获 x 因为它将
Pairs::first
与x
进行比较) - and 运算符必须是 (i) 非静态成员函数或 (ii) 非成员函数。
此外:
- (i) 非静态成员运算符隐式地将
this
作为第一个参数,并且您不能使this
指向Pairs
或Pairs
; - (ii) 非成员函数无法捕获
x
. 的值
我很惊讶还没有建议:
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;
};
}
它需要一个额外的变量,但似乎唯一的选择是递归和一个带有重载二元运算符的包装器 class,在我看来两者都不那么优雅。
||
的短路用于在找到匹配项时停止该功能。
(对于上面的代码,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;
};
}
我决定对参数和 return 类型使用模板参数,但如果需要,您可以推导它们。
请注意,如果您决定既没有默认值也没有 throw
,那么将无效值传递给开关将会给您 UB。
我认为 HolyBlackCat 的解决方案更好,但是...使用求和怎么样?
template <typename ... Pairs>
auto make_switch (Pairs ... ps)
{
return [=](int x)
{ return ( (ps.first == x ? ps.second() : 0) + ... ); };
}
遗憾的是,仅适用于定义总和的类型。