std::optional::value_or() - lazy argument evaluation
Asked Answered
H

4

23

Is it possible to evaluate std::optional::value_or(expr) argument in a lazy way, so the expr were calculated only in the case of having no value?

If not, what would be a proper replacement?

Hydrated answered 5/8, 2018 at 15:12 Comment(0)
M
29
#include <optional>

template <typename F>
struct Lazy
{
    F f;  

    operator decltype(f())() const
    {
        return f();
    }
};

template <typename F>
Lazy(F f) -> Lazy<F>;

int main()
{
    std::optional<int> o;

    int i = o.value_or(Lazy{[]{return 0;}});
}

DEMO

Malaco answered 5/8, 2018 at 15:56 Comment(5)
What kind of construct is template <typename F> Lazy(F f) -> Lazy<F>; ? It looks like a forward declaration of a function template with trailing return type, but it isn't...Jadotville
@BenVoigt it's a template deduction guideMalaco
operator decltype(auto)() const is nicer IMO (less parens). :)Greene
@Greene oh god this breaks my brain... "Hey here's a conversion for... um... well we'll see!"Mirandamire
@Greene There's also operator invoke_result_t<F>()Fragment
S
12

You may write your helper function:

template<typename T, typename F>
T lazy_value_or(const std::optional<T> &opt, F fn) {
    if(opt) return opt.value();
    return fn();
}

which can then be used as:

T t = lazy_value_or(opt, [] { return expensive_computation();});

If that's significantly less typing than doing it explicitly that's up to you to judge; still, you can make it shorter with a macro:

#define LAZY_VALUE_OR(opt, expr) \
    lazy_value_or((opt), [&] { return (expr);})

to be used as

T t = LAZY_VALUE_OR(opt, expensive_calculation());

This is closest to what I think you want, but may be frowned upon as it hides a bit too much stuff.

Shelbyshelden answered 5/8, 2018 at 15:26 Comment(4)
"you can ease this up with some macros" - please don't advocate the use of macros.Dilly
@JesperJuhl just like goto, macros are a tool and have their place, even in modern C++; demonizing them tout court, as for any "absolute" judgment, is wrong and shortsighted. This case is a bit borderline, as it hides quite some stuff (and I even warned about this), but that's up to OP to judge.Shelbyshelden
They are a tool in the toolbox, sure. And they have their uses. But, I don't think we should go around advocating their use since most use of macros is bad or inappropriate - most things can be done better without the use of macros. So yes, they exist. They serve a purpose. Just don't mention them unless they are the last and only resort (IMHO).Dilly
As a naming note, some languages (e.g. Rust) call this value_or_else.Fragment
I
1

C++23 adds a closely related feature: https://en.cppreference.com/w/cpp/utility/optional/or_else

std::optional<int> f();
std::optional<int> g();

std::optional<int> y = f().or_else(g);

Note however that the or_else() argument itself returns std::optional<T> rather than T.

So 3 options, all a little bit clunkier than envisioned:

  • In C++23, use f().or_else(g).value() if available and std::optional<T> return type for the or_else argument isn't a bother.
  • Use a helper function template like lazy_value_or(f(), g), sacrificing the monadic method chain syntax.
  • Write out auto opt = f(); return opt? std::move(*opt) : g();
Innis answered 13/3 at 15:5 Comment(0)
P
0

Make an optional of function type.

Then a lambda can be passed in, which when called will calculate the correct value at the requested moment.

std::optional<std::function<int()>> opt;

int a = 42;
opt = [=] { return a; }

int b = 4;

int c = opt.value_or([=] { return b * 10 + 2;}) ();
Preceding answered 5/8, 2018 at 15:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.