1

In C++(*), is it possible to have a structure that "defers" some computation until needed (and maybe never does the computation if not necessary)? My use case is as follows: I have roughly a dozen bool variables, each of which is computed with some function call. Following that, there is a rather long (and complex) conditional statement that uses those bool variables in different combinations to determine what action the code will take next.

Here is some contrived sample code to hopefully better illustrate what I'm doing:

bool const b1 = func1(param1,param2,param3);
bool const b2 = func2(param4);
// ...
bool const b15 = func15(param35,param36,param37,param38);

if (b1 && !b5 && (b2 || b3)) { do_something1(); }
else if (b3 && !b15 || (b4 && b9 && b6)) { do_something2(); }
else if (b14 || b10 || (!b11 && b7)) { do_something3(); }
else if (b8) {
    if (!b1 || !b6) { do_something4(); }
    else if ( /* ... */ ) // ... etc
}
// ... and on and on

That is a purely contrived example, but hopefully it illustrates the idea.

Clearly this code could be re-written without the bools, and the functions called directly in the big conditional statement. But I feel that would make the already not-easy-to-read code even harder to read, and more error prone. And this logic could change, so I feel the bools make it easier to manage from a refactoring perspective as well.

Furthermore, any bool might be referenced multiple times within the conditional; so using the functions directly means execution could be duplicated. (I was thinking std::bind might get me there from a readability perspective; but it would still potentially call any of the funcN() calls multiple times.)

What I'm looking for is the best of both words, like a "deferred" compute. What if instead of being computed and assigned explicitly at the start of the code, I could say, "only evaluate these as needed (and remember the result)". The big conditional statement is such that, generally, not all bools actually need to be computed to determine what happens next. The goal here is improved performance, as this code is called often. So I'm trying to reduce the amount of work done on each iteration.

(*) Preferably C++14 (or older), as that's what my employer is using.

Edit: What about something like this:

#include <iostream>
#include <functional>

//////////////////////////////////////////////////////////////////////////////
class Sum
{
    public:
        int sum(int const a, int const b) { ++n_calls_; return (a+b); }
        int getNCalls() const { return n_calls_; }

    private:
        int n_calls_ = 0;
};

//////////////////////////////////////////////////////////////////////////////
template <class BoundFunc, typename RetType>
class DeferredCompute
{
    public:
        DeferredCompute(BoundFunc const& f) : func_(f) { }

        RetType operator()()
        {
            if (!computed_)
            {
                value_ = func_();
                computed_ = true;
            }
            return value_;
        }

    private:
        bool             computed_ = false;
        RetType          value_;
        BoundFunc const& func_;
};

//////////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
    Sum s;
    auto boundSum = std::bind(&Sum::sum, &s, 75, 25);

    DeferredCompute<decltype(boundSum), int> deferredSum(boundSum);

    // call function directly repeatedly
    for (int i=0; i<5; ++i)
    {
      std::cout << "boundSum()=" << boundSum() << std::endl;
    }
    std::cout << "s.getNCalls()=" << s.getNCalls() << std::endl;

    // should only call once
    for (int i=0; i<5; ++i)
    {
      std::cout << "deferredSum()=" << deferredSum() << std::endl;
    }
    std::cout << "s.getNCalls()=" << s.getNCalls() << std::endl;

    return 0;
}

Output:

boundSum()=100
boundSum()=100
boundSum()=100
boundSum()=100
boundSum()=100
s.getNCalls()=5
deferredSum()=100
deferredSum()=100
deferredSum()=100
deferredSum()=100
deferredSum()=100
s.getNCalls()=6
Matt
  • 952
  • 2
  • 8
  • 17
  • 6
    Try [std::async](https://en.cppreference.com/w/cpp/thread/async) in `deferred` mode – Alan Birtles Sep 15 '20 at 20:29
  • 3
    There's nothing in the core C++ language your library that does this. But it's not too complicated to implement something that would look exactly like that, something that pretends to be a `bool`, acts like a `bool`, but calls a function the first time it's referenced, and uses the previously evaluated result on all subsequent references. – Sam Varshavchik Sep 15 '20 at 20:31
  • "Is it possible to have a structure that "defers" some computation until needed?" Yes. If you program it, or use a library that provides it. – cmaster - reinstate monica Sep 15 '20 at 20:42
  • 1
    Related (albeit a bit old, still relevant): https://stackoverflow.com/questions/414243/lazy-evaluation-in-c – Eljay Sep 15 '20 at 21:13

4 Answers4

3

std::async with the option std::launch::deferred is what you're looking for.

https://en.cppreference.com/w/cpp/thread/async

eg

auto future = std::async(std::launch::deferred, [](){return 5;});
// future isn't calculated yet

auto result = future.get();
// result = 5, and will remain cached while in scope.
UKMonkey
  • 6,941
  • 3
  • 21
  • 30
  • I'm not sure this will work in my case, see [std::async and lambda function in C++ gives no associated state](https://stackoverflow.com/questions/21967574/stdasync-and-lambda-function-in-c-gives-no-associated-state#21967627). Specifically, "You can't call `get()` twice on the same future'. My understanding of [the get() documentation](https://en.cppreference.com/w/cpp/thread/future/get), is that the future is invalid after the first call to `get()`. Per my example, any of the bools could be referenced more than once, implying multiple future.get() calls. – Matt Sep 17 '20 at 22:48
  • @Matt so what you want isn't how to defer calculation; because that's what a function does anyway - but how to cache the result of the function, so that you only perform the calculation once. They're not the same question. – UKMonkey Sep 18 '20 at 13:26
1

At first, I would try using some lambda-closures.

const auto b1 = [&]() { return func1(param1,param2,param3); };
const auto b2 = [&]() { return func2(param4); };
// ...
const auto b15 = [&]() { return func15(param35,param36,param37,param38); };

if (b1() && !b5() && (b2() || b3())) { do_something1(); }
...

If you need to cache the bool results but not for the entire lifetime of the program (static), this solution could make it (three levels of lambda-closure; it's "Inception").

/**
  g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
      -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
      -g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#include <iostream>

void
test(int i)
{
  auto cache=[](auto expr)
    {
      return [expr, res=false, done=false]() mutable
        {
          if(!done) { res=expr(); done=true; }
          return res;
        };
    };
  auto b1=cache([&]() { std::cout << "(eval b1)"; return i>2; });
  auto b2=cache([&]() { std::cout << "(eval b2)"; return i<5; });
  std::cout << "1: b1=" << b1() << "  b2=" << b2() << '\n';
  std::cout << "2: b1=" << b1() << "  b2=" << b2() << '\n';
}

int
main()
{
  for(int i=0; i<6; ++i)
  {
    std::cout << "~~~~~~~~\n";
    test(i);
  }
  return 0;
}
/**
~~~~~~~~
1: b1=(eval b1)0  b2=(eval b2)1
2: b1=0  b2=1
~~~~~~~~
1: b1=(eval b1)0  b2=(eval b2)1
2: b1=0  b2=1
~~~~~~~~
1: b1=(eval b1)0  b2=(eval b2)1
2: b1=0  b2=1
~~~~~~~~
1: b1=(eval b1)1  b2=(eval b2)1
2: b1=1  b2=1
~~~~~~~~
1: b1=(eval b1)1  b2=(eval b2)1
2: b1=1  b2=1
~~~~~~~~
1: b1=(eval b1)1  b2=(eval b2)0
2: b1=1  b2=0
**/
prog-fh
  • 13,492
  • 1
  • 15
  • 30
  • 2
    Without memoization/caching here, you'll end up rerunning those every time you need the bool though. If you're going to go this route, I'd make a wrapper class with an overloaded `int()` operator that'll only call the function once behind the scenes. – scohe001 Sep 15 '20 at 20:40
0

For the sake of readability and maintainability you could organise the program as a state machine. That provides you with the benefit of separating the state transitions and actions from one another, plus it should be reasonably simple to rewire the logic later should the necessity arise.

See here for some examples: C++ code for state machine

Den-Jason
  • 2,395
  • 1
  • 22
  • 17
0

What if instead of being computed and assigned explicitly at the start of the code, I could say, "only evaluate these as needed (and remember the result)"

/// @brief only evaluate these as needed (and remember the result)
class lazy final
{
    mutable std::future<bool> value_;
public:
    template<typename Functor>
    lazy(Functor &&f)
    : value_{ std::async(std::launch::deferred,
                         std::forward<Functor>(f)) }
    {
    }

    operator bool() const
    {
         return value_.get();
    }
};

client code:

auto b1 = lazy::lazy{[&]{ return func1(param1,param2,param3); }};
auto b2 = lazy::lazy{[&]{ return func2(param4); }};
// ...
bool const b15 = lazy::lazy{[&]{ return func15(param35,param36,param37,param38); }};

// rest remains the same as your contrieved example

I have not compiled this code. If working in c++14 (as you mention) you may need a factory function similar to this:

template<typename Functor>
auto make_lazy(Functor&& f) { return lazy<Functor>(std::forward<Functor>(f)); }

The only thing that changes is the declaration of your bX variables. You may also consider adding code that tells you how often each lazy evaluation is called in practice, declaring those bX variables first, and launching them immediately, in parallel, instead of in a deferred manner. But only do that after you measure performance both ways.

utnapistim
  • 26,809
  • 3
  • 46
  • 82