20

On cppreference, we can see std::optional takes a default value of U&& rather than T&&.

It makes me incapable of writing the following code:

std::optional<std::pair<int, int>> opt;
opt.value_or({1, 2}); // does not compile
opt.value_or(std::make_pair(1, 2)); // compiles

However, I find there is no benefit to using U&&, since U must be convertible to T here.

So, consider the following code, if we have some type U which differs from T, then there will be no perfect match. However, by performing an implicit cast, we can still resolve our call:

template< class U >
constexpr T value_or( T&& default_value ) const&;

I have the following code to test if a template function can take an argument which needs an extra implicit casting to make a perfect match, and it compiles:

#include <cstdio>
#include <optional>
#include <map>

struct A {
    int i = 1;
};

struct B {
    operator A() const {
        return A{2};
    }
};

template <typename T>
struct C {
    void f(T && x) {
        printf("%d\n", x.i);
    }
};

int main() {
    auto b = B();
    C<A> c;
    c.f(b);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
calvin
  • 2,125
  • 2
  • 21
  • 38
  • 3
    To avoid to do extra construction with call similar to `std::optional("some string").value_or("c-string");` – Jarod42 May 24 '23 at 12:53
  • 6
    The answers explain why the proposed change is wrong. But the correct change would be to add the default template argument: `typename U = T`. No idea why that wasn't done. – HolyBlackCat May 24 '23 at 13:03
  • 1
    Unrelated, but simplifies things: `opt.value_or(std::pair{1, 2});` – Ted Lyngmo May 24 '23 at 13:27
  • Another unrelated comment: This Is is a great Q&A. It's a very good question with three very good answers and I've upvoted everything. The only pity is that it shows up on the list of "Unanswered" questions. – Ted Lyngmo Jun 14 '23 at 16:49
  • @TedLyngmo Do I need to accept one of these answers? I find it hard to pick the best because I think all of them are very good... – calvin Jun 15 '23 at 06:56
  • @calvin No, you don't need to. It's just that the question shows up in the list of unanswered questions all the time and this question seems particularly well answered :-) I _suggest_ upvoting them all if you find them all good and then picking one that stands out a little ... even if just a tad. Just a suggestion. – Ted Lyngmo Jun 15 '23 at 07:02

3 Answers3

32

I find there it no benefit by using U&&, since U must be convertible to T here.

U must be convertible to T, but a conversion can be expensive. Taking a forwarding reference (U&&) avoids performing that conversion if the argument is not used (if the object contains a value).

The same cppreference page says value_or is equivalent to:
bool(*this) ? **this : static_cast<T>(std::forward<U>(default_value)).
Here, static_cast<T> performs the conversion, but it is only performed if bool(*this) is false.

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
20

It is to allow perfect forwarding. If the signature were

constexpr T value_or( T&& default_value ) const&;

then T is not something that will be deduced, it is known from the class instantiation, meaning T&& default_value is just a plain rvalue reference to whatever T is.

By using

template< class U >
constexpr T value_or( U&& default_value ) const&;

The U needs to be deduced making it a forwarding reference.

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 1
    or it can be simply overloaded? – apple apple May 24 '23 at 13:01
  • @appleapple It could be, but Nelfeal points out in there answer why you wouldn't want to do that. – NathanOliver May 24 '23 at 13:04
  • true (was about to edit my comment to mention @Jarod42's comment in OP). – apple apple May 24 '23 at 13:07
  • @appleapple actually has a good point, you could provide both the template and additional overloads for T (like `constexpr T value_or( T&& default_value ) const&;`). Only perfect matches would select the non-template overloads. However, I doubt that would offer much value. – Nelfeal May 24 '23 at 13:11
  • Ok, so actually I can think of `U&&` as a universal reference. That makes sense. – calvin May 24 '23 at 14:06
  • @calvin Yes. They've changed the name to forwarding reference. – NathanOliver May 24 '23 at 14:17
  • 1
    That doesn't explain why the type isn't defaulted to `T` though, which would allow the example to compile... – Deduplicator May 25 '23 at 15:56
  • @Deduplicator I'm not sure why it doesn't have a default set. It could be an oversight but it almost feels intentional. I'll try to see if something is mentioned in the optional proposal – NathanOliver May 25 '23 at 16:13
  • 2
    @Deduplicator After finding [this](https://isocpp.org/files/papers/N3672.html#rationale.value_or), it looks like it just might not have been considered. [The original proposal](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1878.htm) is back from 2005-08-29 so they might not even known about the whole `{ ... }` being a non-deduced types, or that the syntax was even going to be a thing. – NathanOliver May 25 '23 at 16:28
  • @NathanOliver-IsonStrike That sounds reasonable. Too bad it didn't trigger a change when [`std::expected::value_or`](https://en.cppreference.com/w/cpp/utility/expected/value_or) was added though. Instead, [the proposal for that](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0323r12.html#value-or) simply says: _"The function member `value_or()` has the same semantics than `optional`"_. – Ted Lyngmo Jun 14 '23 at 08:45
9

As a concrete example

template<class T>
struct constructor {
  operator T()const{
    return f();
  }
  std::function<T()> f;
  constructor( std::function<T()> fin = []{return T{};} ):
    f(std::move(fin))
  {}
};
template<class F>
constructor(F) -> constructor<std::invoke_result_t<F>>;

Now I have an std::optional<std::vector<int>> bob;. I can do:

auto v = bob.value_or( constructor{ []{ return std::vector<int>(10000); } } );

here, if bob has a value, we return it. Otherwise, we create a 10,000 element vector and return that.

Because we defer the conversion from U&& to T for the false branch, we don't have to allocate the 10,000 sized vector unless we need to return it.

Now, I find that adding a T&& overload in these cases is well worth it, and the standard library doesn't do it enough, mostly for the reason you describe -- it permits {} based construction.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Ok, I think I get your point. If I use `T&&`, then the conversion must be done before we pass the `default_value` to `value_or`. However, we can defer the conversion to when we really needs it if we use `U&&`. – calvin May 24 '23 at 13:50
  • 1
    However, I think a better idea is to use `or_else` in this case, which is also recommended way in other languages like Rust. – calvin May 24 '23 at 16:12
  • @calvin `or_else` doesn't exist in C++17 though - but it will come in C++23. – Ted Lyngmo Jun 15 '23 at 10:42