Search code examples
c++c++17foldfold-expression

Retrieve value out of "cascading ifs" fold expression


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 ifs" pattern with a fold expression? I would like to evaluate only as many expressions as required until a matching branch is found.


Solution

  • 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()), true)) || ...)
                /* || (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?)

    Edit:

    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, true) */;
            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.