1
#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是一个简单的函数,它执行以下操作:

  1. 如果变体的值为std::exception_ptr,则重新抛出异常。
  2. 如果变体的值是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 时,我怎么知道我是否应该抛出它?

4

2 回答 2

1

visit不会“优化”代码——它只是在 上匹配的一个很好的模式variant,而且它对于确保你不会忘记任何类型特别有用。

但是其中一个要求visit是每个备选方案都必须返回相同的类型。这在您的用例中尤其成问题,因为应该只返回您的一个替代方案......所以它不适合。您还需要处理 中的monostate情况visit,而您真的没有办法做到这一点(除了...投掷?)所以您只是不走运。

您之前的版本非常好,我只是用类型注释它以更具表现力:

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,12明确地好一点。


另一个问题是,当 type 为 时Tstd::exception_ptr我怎么知道是否应该抛出它?

很简单:你不要扔它。根据您的类型,在通用代码中不要有截然不同的语义。如果它持有 a ,则Promise<T>::await_resume()返回a 。返回一个. 没关系。TTPromise<std::exception_ptr>::await_resume()exception_ptr

我想实际上在我上面的实现中,使用显式get_if<exception_ptr>会变得模棱两可,这是不幸的......所以也许0/ 1/2只是简单的方法。

于 2020-01-22T06:36:03.010 回答
0

参考@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

于 2020-01-22T07:52:19.267 回答