【问题标题】:std::visit and std::variant usagestd::visit 和 std::variant 用法
【发布时间】: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 是一个简单的函数,它执行以下操作:

  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时,我怎么知道该不该扔呢?

【问题讨论】:

    标签: c++ c++17 std-variant


    【解决方案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&lt;T&gt;::await_resume() 包含T,则返回T。 Promise&lt;std::exception_ptr&gt;::await_resume() 返回 exception_ptr。没关系。

    我想实际上在我上面的实现中,使用显式 get_if&lt;exception_ptr&gt; 会变得模棱两可,这很不幸......所以也许 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&lt;T&gt;(&amp;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

    【讨论】:

      猜你喜欢
      • 2021-09-20
      • 1970-01-01
      • 1970-01-01
      • 2021-03-25
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 2018-12-20
      • 1970-01-01
      相关资源
      最近更新 更多