std::visit 和 std::variant 用法
Posted
技术标签:
【中文标题】std::visit 和 std::variant 用法【英文标题】:std::visit and std::variant usage 【发布时间】:2020-01-22 06:11:04 【问题描述】:#include <variant>
#include <exception>
#include <type_traits>
#include <cassert>
template <typename T>
struct Promise
std::variant<
std::monostate,
std::conditional_t<std::is_void_v<T>, std::monostate, T>,
std::exception_ptr
> result_;
T await_resume() const
assert(result_.index() > 0);
#if 1
// old code that I want to optimise
if (result_.index() == 2)
std::rethrow_exception(std::get<2>(result_));
if constexpr (!std::is_void_v<T>)
return std::get<1>(result_);
#else
// new code, won't compile
return std::visit([](auto&& arg)
using TT = std::decay_t<decltype(arg)>;
if constexpr (!std::is_same_v<TT, std::exception_ptr>)
std::rethrow_exception(arg);
else if constexpr (!std::is_void_v<T>)
return arg;
);
#endif
;
template int Promise<int>::await_resume() const;
template std::exception_ptr Promise<std::exception_ptr>::await_resume() const;
template void Promise<void>::await_resume() const;
Promise::await_resume 是一个简单的函数,它执行以下操作:
-
如果变体的值为
std::exception_ptr
,则重新抛出异常。
如果变体的值为T
(虽然T 由用户设置,但也可能是std::exception_ptr),将其返回。如果 T 的类型为 void,则什么也不做。
最初我使用.index()
check 和std::get
来实现它。它可以工作,但是 std::get
事情会在内部产生额外的检查,std::__1::__throw_bad_variant_access()
事情不会发生:https://godbolt.org/z/YnjxDy
我想根据cppreference使用std::visit优化代码,但是编译不出来。
另外一个问题是,当T的类型是std::exception_ptr时,我怎么知道该不该扔呢?
【问题讨论】:
【参考方案1】:visit
不会“优化”代码——它只是在variant
上匹配的一个很好的模式,并且确保您不会忘记任何类型特别有用。
但是visit
的要求之一是每个备选方案都必须返回相同的类型。这在您的用例中尤其成问题,因为应该只返回您的一个替代方案......所以它不适合。您还需要处理visit
中的monostate
案例,而您真的没有办法做到这一点(除了...投掷?)所以您运气不好。
你之前的版本非常好,我只是用类型来注释它以更具表现力:
struct Void ;
template <typename T>
struct Promise
using Value = std::conditional_t<std::is_void_v<T>, Void, T>;
std::variant<
std::monostate,
Value,
std::exception_ptr
> result_;
T await_resume() const
assert(not result_.valueless_by_exception());
assert(not std::holds_alternative<std::monostate>(result_));
if (auto* exc = std::get_if<std::exception_ptr>(&result))
std::rethrow_exception(*exc);
else
if constexpr (not std::is_void_v<T>)
return std::get<T>(result_);
我认为这比明确使用0
、1
和2
好一点。
另外一个问题是
T
的类型是std::exception_ptr
时,怎么知道该不该扔?
简单:你不要扔它。根据您的类型,在通用代码中不要有截然不同的语义。如果Promise<T>::await_resume()
包含T
,则返回T
。 Promise<std::exception_ptr>::await_resume()
返回 exception_ptr
。没关系。
我想实际上在我上面的实现中,使用显式 get_if<exception_ptr>
会变得模棱两可,这很不幸......所以也许 0
/1
/2
只是简单的方法。
【讨论】:
std::variant
是否有像 std::vector::operator[] 那样表现得像 get_or_ub_if_mismatched 的方法?
> 但是访问的要求之一是每个备选方案都必须返回相同的类型。我可以让它们返回相同的类型。只需在rethrow_exception
之后写return T()
。由于rethrow_exception
被声明为no_return
,rethrow_exception
之后的代码不应生成任何代码。但它仍然无法编译。
@CarterLi 1) No. 2) return T();
仅在您的类型是默认可构造的情况下才有帮助,而且arg
可以是T
或monostate
,所以这仍然是不同的类型。
1) 看来*get_if<T>(&variant)
做了我想做的事。因为取消引用 nullptr 是 UB,所以编译器会删除返回 nullptr 的代码路径,包括 if 语句。【参考方案2】:
参考@Barry 的回答,这是我的最终版本:
T await_resume() const
if (auto* pep = std::get_if<2>(&result_))
std::rethrow_exception(*pep);
else
if constexpr (!std::is_void_v<T>)
auto* pv = std::get_if<1>(&result_);
assert(pv);
return *pv;
生成完美的 asm,没有额外的检查,没有 bad_variant_access sh*t:https://godbolt.org/z/96gF_J
【讨论】:
以上是关于std::visit 和 std::variant 用法的主要内容,如果未能解决你的问题,请参考以下文章