std::optional::value_or() 为什么接受 U&& 而不是 T&&?

huangapple go评论90阅读模式
英文:

Why does std::optional::value_or() take a U&& rather than T&&?

问题

cppreference上,我们可以看到std::optional使用默认值U&&而不是T&&

这使我无法编写以下代码:

std::optional<std::pair<int, int>> opt;
opt.value_or({1, 2}); // 无法编译
opt.value_or(std::make_pair(1, 2)); // 可以编译

然而,我发现使用U&&没有任何好处,因为这里U必须可以转换为T

因此,考虑以下代码,如果我们有一些与T不同的类型U,那么就不会有完美匹配。然而,通过执行隐式转换,我们仍然可以解决我们的调用:

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

我有以下代码来测试模板函数是否可以接受需要额外的隐式转换以进行完美匹配的参数,并且它可以编译:

#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);
}
英文:

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);
}

答案1

得分: 32

U 必须可以转换为 T,但是转换可能会昂贵。采用转发引用 (U&&) 可以避免在参数未使用时(如果对象包含一个值)执行该转换。

同一cppreference页面表示 value_or 等效于:
bool(*this) ? **this : static_cast<T>(std::forward<U>(default_value))
在这里,static_cast<T> 执行转换,但仅在 bool(*this)false 时执行。

英文:

> 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&amp;&amp;) 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&lt;T&gt;(std::forward&lt;U&gt;(default_value)).
Here, static_cast&lt;T&gt; performs the conversion, but it is only performed if bool(*this) is false.

答案2

得分: 20

只返回翻译好的部分:

这是为了实现完美转发。如果签名是

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

那么T不会被推断出,它是从类的实例化中已知的,这意味着T&& default_value只是对T的普通右值引用。

通过使用

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

U需要被推断,使其成为一个转发引用。

英文:

It is to allow perfect forwarding. If the signature were

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

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

By using

template&lt; class U &gt;
constexpr T value_or( U&amp;&amp; default_value ) const&amp;;

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

答案3

得分: 9

以下是您要翻译的内容:

作为一个具体的示例

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>>;

现在我有一个 std::optional<std::vector<int>> bob;。我可以这样做:

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

在这里,如果 bob 有一个值,我们返回它。否则,我们创建一个有 10,000 个元素的向量并返回它。

因为我们推迟了从 U&&T 的转换,所以除非需要返回它,否则我们不必分配大小为 10,000 的向量。

现在,我发现在这些情况下添加一个 T&& 重载非常值得,而标准库没有足够这样做,主要是因为您描述的原因 - 它允许 {} 基于构造。

英文:

As a concrete example

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

Now I have an std::optional&lt;std::vector&lt;int&gt;&gt; bob;. I can do:

auto v = bob.value_or( constructor{ []{ return std::vector&lt;int&gt;(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&amp;&amp; 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&amp;&amp; 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.

huangapple
  • 本文由 发表于 2023年5月24日 20:50:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76323736.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定