std::visit 和 std::variant 用法
std::visit and std::variant usage
#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),return就出来了。如果 T 的类型为 void,则什么也不做。
最初我使用 .index()
检查和 std::get
来实现它。它有效,但是 std::get
事情会在内部生成额外的检查,并且 std::__1::__throw_bad_variant_access()
事情不会发生:https://godbolt.org/z/YnjxDy
我想根据cppreference使用std::visit优化代码,但无法编译。
还有一个问题就是,当T的类型是std::exception_ptr的时候,我怎么知道该不该抛?
visit
没有 "optimise" 代码 - 它只是在 variant
上匹配的一个很好的模式,而且它对于确保您没有忘记任何类型特别有用。
但是 visit
的要求之一是每个备选方案必须 return 相同的类型。这在您的用例中尤其成问题,因为您的备选方案中只有一个应该 returned... 所以它不太合适。你还需要处理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
好一点。
Another problem is that when the type of T
is std::exception_ptr
, how can I know whether I should throw it?
很简单:你不扔它。不要在基于你的类型的通用代码中有截然不同的语义。 Promise<T>::await_resume()
return 一个 T
如果它持有一个 T
。 Promise<std::exception_ptr>::await_resume()
return 一个 exception_ptr
。没关系。
我想实际上在我上面的实现中,使用显式 get_if<exception_ptr>
会变得模棱两可,这很不幸......所以也许 0
/1
/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
#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),return就出来了。如果 T 的类型为 void,则什么也不做。
最初我使用 .index()
检查和 std::get
来实现它。它有效,但是 std::get
事情会在内部生成额外的检查,并且 std::__1::__throw_bad_variant_access()
事情不会发生:https://godbolt.org/z/YnjxDy
我想根据cppreference使用std::visit优化代码,但无法编译。
还有一个问题就是,当T的类型是std::exception_ptr的时候,我怎么知道该不该抛?
visit
没有 "optimise" 代码 - 它只是在 variant
上匹配的一个很好的模式,而且它对于确保您没有忘记任何类型特别有用。
但是 visit
的要求之一是每个备选方案必须 return 相同的类型。这在您的用例中尤其成问题,因为您的备选方案中只有一个应该 returned... 所以它不太合适。你还需要处理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
好一点。
Another problem is that when the type of
T
isstd::exception_ptr
, how can I know whether I should throw it?
很简单:你不扔它。不要在基于你的类型的通用代码中有截然不同的语义。 Promise<T>::await_resume()
return 一个 T
如果它持有一个 T
。 Promise<std::exception_ptr>::await_resume()
return 一个 exception_ptr
。没关系。
我想实际上在我上面的实现中,使用显式 get_if<exception_ptr>
会变得模棱两可,这很不幸......所以也许 0
/1
/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