Let's assume that I want to create my own lambda-based switch with the following syntax:
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 );
I would like to use a fold expression in order to have a terse implementation that does not require recursion. The idea is to generate something similar to a bunch of nested if
statements:
if(x == 0) return 0;
if(x == 1) return 50;
if(x == 2) return 100;
I would like to write this:
// pseudocode
template <typename... Pairs>
auto make_switch(Pairs... ps)
{
return [=](int x)
{
( if(ps.first == x) return ps.second(), ... );
};
}
The code above doesn't work as if(...){...}
is not an expression. I then tried to use the &&
operator:
template <typename... Pairs>
auto make_switch(Pairs... ps)
{
return [=](int x)
{
return ((ps.first == x && ps.second()), ...);
};
}
This does compile, but returns the result of ps.first == x && ps.second()
, which is a bool
and not the int
value that I want.
I would like some kind of operator that is a combination between the comma operator and &&
: it should evaluate and evaluate to the right hand side of the operator iff the left hand side evaluates to true
.
I cannot think of any technique that would allow me to implement this in such a way I can get ps.second()
's return value and propagate it to the caller of the lambda returned by make_switch
.
Is it possible to implement this kind of "cascading if
s" pattern with a fold expression? I would like to evaluate only as many expressions as required until a matching branch is found.
I'm surprised it wasn't suggested yet:
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)
It requires an additional variable, but it seems the only alternatives are recursion and a wrapper class with an overloaded binary operator, and both look less elegant to me.
The short-circuiting of ||
is used to stop the function when a match is found.
(For the above code GCC 7.2 gives me warning: suggest parentheses around '&&' within '||'
. Probably a bug?)
Here's a version generalized for any types: (credits to @Barry for suggesting 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)
I decided to use template parameters for parameter and return types, but you can deduce them if you want.
Note that if you decide to not have a default value nor throw
, then passing an invalid value to the switch will give you UB.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With