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

cal*_*vin 21 c++ c++17 stdoptional

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

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

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

但是,我发现使用 没有任何好处U&&,因为U必须可以转换为T此处。

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

template< class U >
constexpr T value_or( T&& default_value ) const&;
Run Code Online (Sandbox Code Playgroud)

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

#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);
}
Run Code Online (Sandbox Code Playgroud)

Nel*_*eal 32

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

U必须可转换为T,但转换成本可能很高。U&&如果不使用参数(如果对象包含值),则采用转发引用 ( ) 可以避免执行该转换。

同一个 cppreference 页面说value_or相当于:
bool(*this) ? **this : static_cast<T>(std::forward<U>(default_value))
此处,static_cast<T>执行转换,但仅当bool(*this)is时才执行false


Nat*_*ica 20

是为了允许完美转发。如果签名是

constexpr T value_or( T&& default_value ) const&;
Run Code Online (Sandbox Code Playgroud)

thenT不是会被推导出来的东西,它是从类实例化中得知的,含义T&& default_value只是对任何内容的简单右值引用T

通过使用

template< class U >
constexpr T value_or( U&& default_value ) const&;
Run Code Online (Sandbox Code Playgroud)

需要U推断出使其成为转发参考。

  • @Deduplicator 找到[this](https://isocpp.org/files/papers/N3672.html#rationale.value_or)后,看起来可能没有考虑到它。[原始提案](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1878.htm) 于 2005 年 8 月 29 日返回,因此他们可能甚至不知道整个“{ ... }”是非推导类型,或者语法甚至将成为一件事。 (2认同)

Yak*_*ont 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>>;
Run Code Online (Sandbox Code Playgroud)

现在我有一个std::optional<std::vector<int>> bob;. 我可以:

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

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

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

现在,我发现T&&在这些情况下添加重载是非常值得的,而标准库做得还不够,主要是出于您所描述的原因——它允许{}基于构造。