Clang LLVM C++ `std::optional<std::optional<int>>` 令人惊讶的比较行为
Clang LLVM C++ `std::optional<std::optional<int>>` surprising comparison behavior
我有一个函数 load(std::optional<int> page)
,如果 page.empty()
,它可以加载给定页面或所有页面。因为加载是一项代价高昂的操作,所以我缓存了最后加载的页面及其内容。为此,我使用类型为 std::optional<std::optional<int>>
的成员变量,其值应该告诉我当前缓存的是单个页面、所有页面还是根本没有页面。
LLVM 的 libc++ 实现(随 clang Apple LLVM version 10.0.0 (clang-1000.11.45.2)
一起提供)在比较 std::optional 实例时有一个令人惊讶的行为,这与 boost::optional
所做的不同(用1.67):
std::cout << (std::optional<int>() == std::optional<std::optional<int>>()); // prints 1
std::cout << (boost::optional<int>() == boost::optional<boost::optional<int>>()); // prints 0
哪个是正确的行为,这是 libc++ 实现中的错误吗?
行为正确:
https://en.cppreference.com/w/cpp/utility/optional/operator_cmp
template< class T, class U >
constexpr bool operator!=( const optional<T>& lhs, const optional<U>& rhs ); (2)
Performs comparison operations on optional objects.
1-6) Compares two optional objects, lhs and rhs. The contained values
are compared (using the corresponding operator of T) only if both lhs
and rhs contain values. Otherwise,
- lhs is considered equal to rhs if, and only if, both lhs and rhs do
not contain a value.
std::optional<T>
的默认构造函数构造一个不包含值的对象,因此 std::optional<int>>()
和 std::optional<std::optional<int>>()
都不包含值,因此它们是相等的。
作为旁注(但无论如何我们都在这里)我个人不太喜欢你的设计。作为您代码的使用者,我希望 load(<empty optional>)
加载...没有页面。这就是可选的意思。
一种解决方案是具有两种不同的功能:
void load(int);
void load_all();
如果这对您来说过于激进,那么您可以通过多种方式进行重载:
struct load_all{};
void load(int);
void load(load_all);
我真的很喜欢明确说明我的要求和我得到的东西。这就是为什么我更喜欢上面的解决方案而不是下一个解决方案:
void load(int);
void load();
为了完整性,虽然我实际上并不推荐它(因为使用 std::variant
的工具很麻烦,否则这很好地表达了意图):
struct load_all{};
void load(std::variant<int, load_all>);
这个其实挺有意思的……模棱两可的行为也是善意的。对于比较 应该 做什么,没有明确的答案。基于实现选择,标准库和 Boost 只是做不同的事情。没有人是错的。
std::optional
有不同的比较。这意味着有运算符将 optional<T>
与 optional<U>
和 U
进行比较。在此模型中,最佳匹配是将双方视为 可选 - 因此它们比较相等,因为它们都脱离了。
但是boost::optional
只有相同类型的比较。这意味着 optional<T>
只能与 optional<T>
或 T
相媲美。在此模型中,optional<int>
被解释为 值 的 optional<optional<int>>
。所以我们有一个脱离的可选值和一个值 - 所以它们比较不相等。
我不确定这个比较是否真的有明确的意思,所以最好避免它。
我有一个函数 load(std::optional<int> page)
,如果 page.empty()
,它可以加载给定页面或所有页面。因为加载是一项代价高昂的操作,所以我缓存了最后加载的页面及其内容。为此,我使用类型为 std::optional<std::optional<int>>
的成员变量,其值应该告诉我当前缓存的是单个页面、所有页面还是根本没有页面。
LLVM 的 libc++ 实现(随 clang Apple LLVM version 10.0.0 (clang-1000.11.45.2)
一起提供)在比较 std::optional 实例时有一个令人惊讶的行为,这与 boost::optional
所做的不同(用1.67):
std::cout << (std::optional<int>() == std::optional<std::optional<int>>()); // prints 1
std::cout << (boost::optional<int>() == boost::optional<boost::optional<int>>()); // prints 0
哪个是正确的行为,这是 libc++ 实现中的错误吗?
行为正确:
https://en.cppreference.com/w/cpp/utility/optional/operator_cmp
template< class T, class U > constexpr bool operator!=( const optional<T>& lhs, const optional<U>& rhs ); (2)
Performs comparison operations on optional objects.
1-6) Compares two optional objects, lhs and rhs. The contained values are compared (using the corresponding operator of T) only if both lhs and rhs contain values. Otherwise,
- lhs is considered equal to rhs if, and only if, both lhs and rhs do not contain a value.
std::optional<T>
的默认构造函数构造一个不包含值的对象,因此 std::optional<int>>()
和 std::optional<std::optional<int>>()
都不包含值,因此它们是相等的。
作为旁注(但无论如何我们都在这里)我个人不太喜欢你的设计。作为您代码的使用者,我希望 load(<empty optional>)
加载...没有页面。这就是可选的意思。
一种解决方案是具有两种不同的功能:
void load(int);
void load_all();
如果这对您来说过于激进,那么您可以通过多种方式进行重载:
struct load_all{};
void load(int);
void load(load_all);
我真的很喜欢明确说明我的要求和我得到的东西。这就是为什么我更喜欢上面的解决方案而不是下一个解决方案:
void load(int);
void load();
为了完整性,虽然我实际上并不推荐它(因为使用 std::variant
的工具很麻烦,否则这很好地表达了意图):
struct load_all{};
void load(std::variant<int, load_all>);
这个其实挺有意思的……模棱两可的行为也是善意的。对于比较 应该 做什么,没有明确的答案。基于实现选择,标准库和 Boost 只是做不同的事情。没有人是错的。
std::optional
有不同的比较。这意味着有运算符将 optional<T>
与 optional<U>
和 U
进行比较。在此模型中,最佳匹配是将双方视为 可选 - 因此它们比较相等,因为它们都脱离了。
但是boost::optional
只有相同类型的比较。这意味着 optional<T>
只能与 optional<T>
或 T
相媲美。在此模型中,optional<int>
被解释为 值 的 optional<optional<int>>
。所以我们有一个脱离的可选值和一个值 - 所以它们比较不相等。
我不确定这个比较是否真的有明确的意思,所以最好避免它。